Index: messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/MessageForumsMessageManagerImpl.java
===================================================================
--- messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/MessageForumsMessageManagerImpl.java (revision 121251)
+++ messageforums-component-impl/src/java/org/sakaiproject/component/app/messageforums/MessageForumsMessageManagerImpl.java (working copy)
@@ -44,6 +44,7 @@
import org.sakaiproject.api.app.messageforums.Message;
import org.sakaiproject.api.app.messageforums.MessageForumsMessageManager;
import org.sakaiproject.api.app.messageforums.MessageForumsTypeManager;
+import org.sakaiproject.api.app.messageforums.MessageMoveHistory;
import org.sakaiproject.api.app.messageforums.PrivateMessage;
import org.sakaiproject.api.app.messageforums.SynopticMsgcntrManager;
import org.sakaiproject.api.app.messageforums.Topic;
@@ -51,6 +52,7 @@
import org.sakaiproject.api.app.messageforums.cover.SynopticMsgcntrManagerCover;
import org.sakaiproject.component.app.messageforums.dao.hibernate.AttachmentImpl;
import org.sakaiproject.component.app.messageforums.dao.hibernate.MessageImpl;
+import org.sakaiproject.component.app.messageforums.dao.hibernate.MessageMoveHistoryImpl;
import org.sakaiproject.component.app.messageforums.dao.hibernate.PrivateMessageImpl;
import org.sakaiproject.component.app.messageforums.dao.hibernate.UnreadStatusImpl;
import org.sakaiproject.component.app.messageforums.dao.hibernate.Util;
@@ -89,6 +91,8 @@
private static final String QUERY_FIND_PENDING_MSGS_BY_CONTEXT_AND_USER_AND_PERMISSION_LEVEL_NAME = "findAllPendingMsgsByContextByMembershipByPermissionLevelName";
private static final String QUERY_FIND_PENDING_MSGS_BY_TOPICID = "findPendingMsgsByTopicId";
private static final String QUERY_UNDELETED_MSG_BY_TOPIC_ID = "findUndeletedMessagesByTopicId";
+ private static final String QUERY_MOVED_MESSAGES_BY_TOPICID = "findMovedMessagesByTopicId";
+ private static final String QUERY_MOVED_HISTORY_BY_MESSAGEID = "findMovedHistoryByMessageId";
//private static final String ID = "id";
private static final String MESSAGECENTER_HELPER_TOOL_ID = "sakai.messageforums.helper";
@@ -1867,4 +1871,88 @@
LOG.debug("about to return");
return Util.setToList(resultSet);
}
+
+
+ public void saveMessageMoveHistory(Long messageId, Long desttopicId, Long sourceTopicId, boolean checkReminder){
+ if (messageId == null || desttopicId == null || sourceTopicId == null) {
+ LOG.error("saveMessageMoveHistory failed with desttopicId: " + desttopicId + ", messageId: " + messageId + ", sourceTopicId: " + sourceTopicId);
+ throw new IllegalArgumentException("Null Argument");
}
+
+ if (LOG.isDebugEnabled()) LOG.debug("saveMessageMoveHistory executing with desttopicId: " + desttopicId + ", messageId: " + messageId + ", sourceTopicId: " + sourceTopicId);
+
+
+ List moved_history = null;
+
+ moved_history = this.findMovedHistoryByMessageId(messageId);
+
+ // if moving back to the original topic, set reminder to false, otherwise the original topic will show a Move reminder.
+ if (LOG.isDebugEnabled()) LOG.debug("saveMessageMoveHistory (moved_messages size " + moved_history.size() );
+ for (Iterator histIter = moved_history.iterator(); histIter.hasNext();) {
+ MessageMoveHistory hist = (MessageMoveHistory) histIter.next();
+ if (LOG.isDebugEnabled()) LOG.debug("moved message ids = " + hist.getId() + " from : " + hist.getFromTopicId() + " topic : " + hist.getToTopicId() );
+ if (hist.getFromTopicId().equals(desttopicId)){
+ hist.setReminder(false);
+ hist.setModified(new Date());
+ hist.setModifiedBy(getCurrentUser());
+ getHibernateTemplate().update(hist);
+ }
+ }
+
+ MessageMoveHistory mhist = new MessageMoveHistoryImpl ();
+
+ mhist.setToTopicId(desttopicId);
+ mhist.setMessageId(messageId);
+ mhist.setFromTopicId(sourceTopicId);
+ mhist.setReminder(checkReminder);
+ mhist.setUuid(getNextUuid());
+ mhist.setCreated(new Date());
+ mhist.setCreatedBy(getCurrentUser());
+ mhist.setModified(new Date());
+ mhist.setModifiedBy(getCurrentUser());
+
+ getHibernateTemplate().saveOrUpdate(mhist);
+
+
+ }
+
+ public List findMovedMessagesByTopicId(final Long topicId) {
+ if (topicId == null) {
+ LOG.error("findMovedMessagesByTopicId failed with topicId: " + topicId);
+ throw new IllegalArgumentException("Null Argument");
+ }
+
+ if (LOG.isDebugEnabled()) LOG.debug("findMovedMessagesByTopicId executing with topicId: " + topicId);
+
+ HibernateCallback hcb = new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException, SQLException {
+ Query q = session.getNamedQuery(QUERY_MOVED_MESSAGES_BY_TOPICID);
+ q.setParameter("topicId", topicId, Hibernate.LONG);
+ return q.list();
+ }
+ };
+
+ return (List) getHibernateTemplate().execute(hcb);
+ }
+
+ public List findMovedHistoryByMessageId(final Long messageid){
+ if (messageid == null) {
+ LOG.error("findMovedHistoryByMessageId failed with messageid: " + messageid);
+ throw new IllegalArgumentException("Null Argument");
+ }
+
+ if (LOG.isDebugEnabled()) LOG.debug("findMovedHistoryByMessageId executing with messageid: " + messageid);
+
+ HibernateCallback hcb = new HibernateCallback() {
+ public Object doInHibernate(Session session) throws HibernateException, SQLException {
+ Query q = session.getNamedQuery(QUERY_MOVED_HISTORY_BY_MESSAGEID);
+ q.setParameter("messageId", messageid, Hibernate.LONG);
+ return q.list();
+ }
+ };
+
+ return (List) getHibernateTemplate().execute(hcb);
+
+ }
+
+}
Index: messageforums-components/src/webapp/WEB-INF/components.xml
===================================================================
--- messageforums-components/src/webapp/WEB-INF/components.xml (revision 121251)
+++ messageforums-components/src/webapp/WEB-INF/components.xml (working copy)
@@ -19,6 +19,7 @@
org/sakaiproject/component/app/messageforums/dao/hibernate/EmailNotification.hbm.xmlorg/sakaiproject/component/app/messageforums/dao/hibernate/SynopticMsgcntrItem.hbm.xmlorg/sakaiproject/component/app/messageforums/dao/hibernate/HiddenGroupImpl.hbm.xml
+ org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistory.hbm.xml
Index: messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml
===================================================================
--- messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml (revision 121251)
+++ messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageImpl.hbm.xml (working copy)
@@ -718,7 +718,22 @@
message.deleted = false]]>
+
+
+
+
+
+
+
+
Index: messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistoryImpl.java
===================================================================
--- messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistoryImpl.java (revision 0)
+++ messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistoryImpl.java (revision 0)
@@ -0,0 +1,63 @@
+/**********************************************************************************
+ * $URL: $
+ * $Id: $
+ ***********************************************************************************
+ *
+ * Copyright (c) 2003, 2004, 2005, 2006, 2008 The Sakai Foundation
+ *
+ * Licensed under the Educational Community License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.osedu.org/licenses/ECL-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **********************************************************************************/
+package org.sakaiproject.component.app.messageforums.dao.hibernate;
+
+import org.sakaiproject.api.app.messageforums.MessageMoveHistory;
+
+public class MessageMoveHistoryImpl extends MutableEntityImpl implements MessageMoveHistory {
+ private Long fromTopicId;
+ private Long messageId;
+ private Boolean reminder;
+ private Long toTopicId;
+
+ public Long getFromTopicId() {
+ return fromTopicId;
+ }
+
+ public Long getMessageId() {
+ return messageId;
+ }
+
+ public Boolean getReminder() {
+ return reminder;
+ }
+
+ public Long getToTopicId() {
+ return toTopicId;
+ }
+
+ public void setFromTopicId(Long topicId) {
+ this.fromTopicId = topicId;
+ }
+
+ public void setMessageId(Long messageId) {
+ this.messageId = messageId;
+ }
+
+ public void setReminder(Boolean remind) {
+ this.reminder = remind;
+ }
+
+ public void setToTopicId(Long topicId) {
+ this.toTopicId = topicId;
+
+ }
+}
Property changes on: messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistoryImpl.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistory.hbm.xml
===================================================================
--- messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistory.hbm.xml (revision 0)
+++ messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistory.hbm.xml (revision 0)
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+
+
+
+
+ MFR_MOVE_HISTORY_S
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ create index MFR_MOVE_HISTORY_MSG_ID on MFR_MOVE_HISTORY_T (MESSAGE)
+
+
+
+
+
Property changes on: messageforums-hbm/src/java/org/sakaiproject/component/app/messageforums/dao/hibernate/MessageMoveHistory.hbm.xml
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageForumsMessageManager.java
===================================================================
--- messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageForumsMessageManager.java (revision 121251)
+++ messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageForumsMessageManager.java (working copy)
@@ -241,4 +241,8 @@
* @return a list of messages
*/
public List getAllMessagesInSite(String siteId);
+
+ public void saveMessageMoveHistory(Long msgid, Long desttopicId,Long sourceTopicId, boolean checkreminder);
+
+ public List findMovedMessagesByTopicId(Long id);
}
\ No newline at end of file
Index: messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java
===================================================================
--- messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java (revision 121251)
+++ messageforums-api/src/java/org/sakaiproject/api/app/messageforums/DiscussionForumService.java (working copy)
@@ -75,6 +75,8 @@
public static final String EVENT_FORUMS_GRADE = "forums.grade";
+ public static final String EVENT_FORUMS_MOVE_THREAD = "forums.movethread";
+
/** Used to determine if MessageCenter tool part of site */
public static final String MESSAGE_CENTER_ID = "sakai.messagecenter";
Index: messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageMoveHistory.java
===================================================================
--- messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageMoveHistory.java (revision 0)
+++ messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageMoveHistory.java (revision 0)
@@ -0,0 +1,48 @@
+/**********************************************************************************
+ * $URL: $
+ * $Id: $
+ ***********************************************************************************
+ *
+ * Copyright (c) 2003, 2004, 2005, 2006, 2008 The Sakai Foundation
+ *
+ * Licensed under the Educational Community License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.osedu.org/licenses/ECL-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ **********************************************************************************/
+package org.sakaiproject.api.app.messageforums;
+
+public interface MessageMoveHistory extends MutableEntity{
+
+ public Long getId();
+
+ public void setId(Long id);
+
+ public Integer getVersion();
+
+ public void setVersion(Integer version);
+
+ public Long getMessageId();
+
+ public void setMessageId(Long messageId);
+
+ public Long getFromTopicId();
+
+ public void setFromTopicId(Long topicId);
+
+ public Long getToTopicId();
+
+ public void setToTopicId(Long topicId);
+
+ public Boolean getReminder();
+
+ public void setReminder(Boolean remind);
+}
\ No newline at end of file
Property changes on: messageforums-api/src/java/org/sakaiproject/api/app/messageforums/MessageMoveHistory.java
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties
===================================================================
--- messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties (revision 121251)
+++ messageforums-api/src/bundle/org/sakaiproject/api/app/messagecenter/bundle/Messages.properties (working copy)
@@ -749,3 +749,20 @@
hiddenGroups_addGroup=Add Group
hiddenGroups_selectGroup=--Select Group--
hiddenGroups_remove=Remove
+
+#MSGCNTR-241
+move_thread=Move Thread(s)
+by_name= By Name:
+in_forum= In Forum:
+move_thread_info1= The following thread(s) will be moved from
+move_thread_info2= to the topic selected below.
+leave_reminder=Leave reminder about the move in the old topic
+filter_topics=Filter Topics
+topics=Topics
+showing=showing
+of=of
+move_button_text=Move Thread(s) to Selected Topic
+locked_topics_hidden=Locked topics will not show up as available options
+hasBeen=has been
+moved=moved
+anotherTopic=to another topic.
\ No newline at end of file
Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/jsf/HierDataTableRender.java
===================================================================
--- messageforums-app/src/java/org/sakaiproject/tool/messageforums/jsf/HierDataTableRender.java (revision 121251)
+++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/jsf/HierDataTableRender.java (working copy)
@@ -253,16 +253,19 @@
writer.startElement("tr", data);
+ boolean display_moveCheckbox = false;
// if this row should be hidden initially, setup those styles/classes
if (currentThread == null || !tmpMsg.getId().equals(currentThread.getId())) {
writer.writeAttribute("class", "hierItemBlock", null);
currentThread = tmpMsg;
displayToggle = !noArrows && dmb.getChildCount() > 0;
+ display_moveCheckbox = true;
dmb.setDepth(0);
} else if (!noArrows) {
writer.writeAttribute("style", "display: none", null);
writer.writeAttribute("id", "_id_" + dmb.getMessage().getId() + "__hide_division_", null);
checkExpanded = true;
+ display_moveCheckbox = false;
}
if (!noArrows && dmb.getMessage().getInReplyTo() != null) {
@@ -289,6 +292,26 @@
if (currColumnClass >= columnClasses.length) currColumnClass = 0;
}
+ // if hierItemBlock
+ if ((display_moveCheckbox) && (column.getId().endsWith("_checkbox")) &&
+ (dmb.getRevise()) && (!dmb.getDeleted())) {
+ writer.startElement("input", null);
+ writer.writeAttribute("id", "moveCheckbox", null);
+ writer.writeAttribute("type", "checkbox", null);
+ writer.writeAttribute("name", "moveCheckbox", null);
+ writer.writeAttribute("onclick", "enableDisableMoveThreadLink();", null);
+ writer.writeAttribute("value", dmb.getMessage().getId(), null);
+ writer.endElement("input");
+ writer.endElement("td");
+ continue;
+ }
+ else {
+ if (column.getId().endsWith("_checkbox")) {
+ writer.endElement("td");
+ continue;
+ }
+ }
+
if (displayToggle) {
// write the toggle td if necessary
if (dmb.getChildCount() > 0) {
Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/DiscussionForumTool.java
===================================================================
--- messageforums-app/src/java/org/sakaiproject/tool/messageforums/DiscussionForumTool.java (revision 121251)
+++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/DiscussionForumTool.java (working copy)
@@ -21,6 +21,7 @@
package org.sakaiproject.tool.messageforums;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
@@ -49,8 +50,14 @@
import javax.faces.context.FacesContext;
import javax.faces.event.ValueChangeEvent;
import javax.faces.model.SelectItem;
+import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
+import net.sf.json.JSON;
+import net.sf.json.JSONObject;
+import net.sf.json.JSONSerializer;
+import net.sf.json.JsonConfig;
+
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.sakaiproject.api.app.messageforums.Area;
@@ -67,6 +74,7 @@
import org.sakaiproject.api.app.messageforums.Message;
import org.sakaiproject.api.app.messageforums.MessageForumsMessageManager;
import org.sakaiproject.api.app.messageforums.MessageForumsTypeManager;
+import org.sakaiproject.api.app.messageforums.MessageMoveHistory;
import org.sakaiproject.api.app.messageforums.OpenForum;
import org.sakaiproject.api.app.messageforums.PermissionLevel;
import org.sakaiproject.api.app.messageforums.PermissionLevelManager;
@@ -84,6 +92,7 @@
import org.sakaiproject.authz.cover.AuthzGroupService;
import org.sakaiproject.authz.cover.SecurityService;
import org.sakaiproject.component.app.messageforums.MembershipItem;
+import org.sakaiproject.component.app.messageforums.dao.hibernate.MessageMoveHistoryImpl;
import org.sakaiproject.component.app.messageforums.dao.hibernate.util.comparator.ForumBySortIndexAscAndCreatedDateDesc;
import org.sakaiproject.component.cover.ComponentManager;
import org.sakaiproject.component.cover.ServerConfigurationService;
@@ -139,6 +148,7 @@
private static final String TOPIC_SETTING = "dfTopicSettings";
private static final String TOPIC_SETTING_REVISE = "dfReviseTopicSettings";
private static final String MESSAGE_COMPOSE = "dfCompose";
+ private static final String MESSAGE_MOVE_THREADS= "dfMoveThreads";
private static final String MESSAGE_VIEW = "dfViewMessage";
private static final String THREAD_VIEW = "dfViewThread";
private static final String ALL_MESSAGES = "dfAllMessages";
@@ -370,6 +380,16 @@
private String editorRows;
+ private boolean threadMoved;
+
+ public boolean isThreadMoved() {
+ return threadMoved;
+ }
+
+ public void setThreadMoved(boolean threadMoved) {
+ this.threadMoved = threadMoved;
+ }
+
/**
*
*/
@@ -2449,6 +2469,7 @@
Boolean foundHead = false;
Boolean foundAfterHead = false;
+ threadMoved = didThreadMove();
//determine to make sure that selectedThreadHead does exist!
if(selectedThreadHead == null){
@@ -2472,8 +2493,10 @@
}
}
formatMessagesByRemovelastEmptyLines(msgsList);
+ if (!threadMoved) {
recursiveGetThreadedMsgsFromList(msgsList, orderedList, selectedThreadHead);
selectedThread.addAll(orderedList);
+ }
// now process the complete list of messages in the selected thread to possibly flag as read
// if this topic is flagged to autoMarkThreadsRead, mark each message in the thread as read
@@ -2492,6 +2515,22 @@
return THREAD_VIEW;
}
+
+ private boolean didThreadMove() {
+ threadMoved = false;
+ String message = selectedThreadHead.getMessage().toString();
+ List msgsList = selectedTopic.getMessages();
+ boolean listHasMessage = false;
+ for (int i = 0; i < msgsList.size(); i++) {
+ listHasMessage = message.equals(((DiscussionMessageBean) msgsList.get(i)).getMessage().toString());
+ if (listHasMessage) {
+ break;
+ }
+ }
+ threadMoved = !listHasMessage;
+ return threadMoved;
+ }
+
/**
* remove last empty lines of every massage in thread view
*/
@@ -3122,14 +3161,30 @@
temp_messages = forumManager.getTopicByIdWithMessagesAndAttachments(topic.getId())
.getMessages();
}
+
+ // Now get messages moved from this topic
+
+ List moved_messages = null;
+ if(uiPermissionsManager.isRead(topic, selectedForum.getForum())){
+ moved_messages = messageManager.findMovedMessagesByTopicId(topic.getId());
+ }
+
+ if (LOG.isDebugEnabled())
+ {
+ LOG.debug("getDecoratedTopic(moved_messages size " + moved_messages.size() );
+ for (Iterator msgIter = moved_messages.iterator(); msgIter.hasNext();) {
+ Message msg = (Message) msgIter.next();
+ LOG.debug("moved message ids = " + msg.getId() + " title : " + msg.getTitle() + " moved to topic : " + msg.getTopic().getId() );
+ }
+ }
+
+ List msgIdList = new ArrayList();
if (temp_messages == null || temp_messages.size() < 1)
{
decoTopic.setTotalNoMessages(0);
decoTopic.setUnreadNoMessages(0);
- return decoTopic;
}
-
- List msgIdList = new ArrayList();
+ else {
for (Iterator msgIter = temp_messages.iterator(); msgIter.hasNext();) {
Message msg = (Message) msgIter.next();
if(msg != null && !msg.getDraft().booleanValue() && !msg.getDeleted()) {
@@ -3140,10 +3195,10 @@
// retrieve read status for all of the messages in this topic
Map messageReadStatusMap=null;
if(getUserId()!= null){
- LOG.debug("getting unread counts for " + getUserId());
+ if (LOG.isDebugEnabled()) LOG.debug("getting unread counts for " + getUserId());
messageReadStatusMap = forumManager.getReadStatusForMessagesWithId(msgIdList, getUserId());
}else if(getUserId() == null && this.forumManager.getAnonRole()==true){
- LOG.debug("getting unread counts for anon user");
+ if (LOG.isDebugEnabled()) LOG.debug("getting unread counts for anon user");
messageReadStatusMap = forumManager.getReadStatusForMessagesWithId(msgIdList, ".anon");
}
@@ -3213,6 +3268,26 @@
}
}
}
+ // now add moved messages to decoTopic
+ for (Iterator msgIter = moved_messages.iterator(); msgIter.hasNext();) {
+ Message message = (Message) msgIter.next();
+ if (message != null)
+ {
+ // load topic, it was not fully loaded.
+ Topic desttopic = message.getTopic();
+ Topic fulltopic = forumManager.getTopicById(message.getTopic().getId());
+ message.setTopic(fulltopic);
+ if (LOG.isDebugEnabled()) LOG.debug("message.getTopic() id " + message.getTopic().getId());
+ if (LOG.isDebugEnabled()) LOG.debug("message.getTopic() title" + message.getTopic().getTitle());
+
+ DiscussionMessageBean decoMsg = new DiscussionMessageBean(message,
+ messageManager);
+ decoMsg.setMoved(true);
+ decoTopic.addMessage(decoMsg);
+ }
+ }
+
+ }
return decoTopic;
}
@@ -8763,5 +8838,166 @@
public String getDefaultAvailabilityTime(){
return ServerConfigurationService.getString("msgcntr.forums.defaultAvailabilityTime", "").toLowerCase();
}
+
+ // MSGCNTR-241 move threads
+ public String processMoveMessage() {
+ return MESSAGE_MOVE_THREADS;
}
+ public String getMoveThreadJSON() {
+ List allItemsList = new ArrayList();
+
+ Map> topicMap = null;
+ Map> forumMap = null;
+ List allforums = forumManager.getDiscussionForumsWithTopics(this.getSiteId());
+ if (allforums != null) {
+ Iterator iter = allforums.iterator();
+ if (allforums == null || allforums.size() < 1) {
+ return null;
+ }
+ topicMap = new HashMap>();
+ forumMap = new HashMap>();
+ topicMap.put("topics", new ArrayList());
+ forumMap.put("forums", new ArrayList());
+ while (iter.hasNext()) {
+ DiscussionForum tmpforum = (DiscussionForum) iter.next();
+ parseForums(tmpforum, forumMap);
+ if (tmpforum != null) {
+ for (Iterator itor = tmpforum.getTopicsSet().iterator(); itor.hasNext();) {
+ DiscussionTopic topic = (DiscussionTopic) itor.next();
+ if (tmpforum.getLocked() == null || tmpforum.getLocked().equals(Boolean.TRUE)) {
+ // do nothing. Skip forums that are locked. topics in locked forums should not show in the dialog
+ } else if (topic.getLocked() == null || topic.getLocked().equals(Boolean.TRUE)) {
+ // do nothing, skip locked topics. do not show them in move thread dialog
+ } else {
+ parseTopics(topic, topicMap, tmpforum);
+ }
+ }
+ }
+
+ }
+ allItemsList.add(topicMap);
+ allItemsList.add(forumMap);
+ }
+
+ JsonConfig config = new JsonConfig();
+ JSON json = JSONSerializer.toJSON(allItemsList);
+ if (LOG.isDebugEnabled())
+ LOG.debug("converted getTotalTopicsJSON to json : " + json.toString(4, 0));
+ return json.toString(4, 0);
+ }
+
+ private void parseForums(DiscussionForum forum, Map> forumMap) {
+ Long forumId = forum.getId();
+ String forumtitle = forum.getTitle();
+ Long forumid = forum.getId();
+ List forumList = forumMap.get("forums");
+ if (forumList == null) {
+ forumList = new ArrayList();
+ }
+
+ JSONObject forumJSON = new JSONObject();
+ forumJSON.element("forumid", forumId).element("forumtitle", forumtitle);
+ forumList.add(forumJSON);
+ }
+
+ private void parseTopics(DiscussionTopic topic, Map> topicMap, DiscussionForum tmpforum) {
+ Long topicId = topic.getId();
+ String forumtitle = tmpforum.getTitle();
+ Long forumid = tmpforum.getId();
+ List topiclist = topicMap.get("topics");
+ if (topiclist == null) {
+ topiclist = new ArrayList();
+ }
+ String title = topic.getTitle();
+ JSONObject topicJSON = new JSONObject();
+ topicJSON.element("topicid", topic.getId()).element("topictitle", title).element("forumid", forumid)
+ .element("forumtitle", forumtitle);
+ topiclist.add(topicJSON);
+ }
+
+ public List getRequestParamArray(String paramPart) {
+ // FacesContext context = FacesContext.getCurrentInstance();
+ // Map requestParams = context.getExternalContext().getRequestParameterMap();
+
+ HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
+ Map requestParams = req.getParameterMap();
+ String[] result = (String[]) requestParams.get(paramPart);
+ return Arrays.asList(result);
+ }
+
+ public String processMoveThread() {
+ Long sourceTopicId = this.selectedTopic.getTopic().getId();
+ if (LOG.isDebugEnabled()) LOG.debug("Calling processMoveThread source topic is " + sourceTopicId);
+ List checkedThreads = getRequestParamArray("moveCheckbox");
+ List destTopicList = getRequestParamArray("selectedTopicid");
+
+ String desttopicIdstr = null;
+
+ if (destTopicList.size() != 1) {
+ // do nothing, there should be one and only one destination.
+ return gotoMain();
+ } else {
+ desttopicIdstr = (String) destTopicList.get(0);
+ }
+ if (LOG.isDebugEnabled()) LOG.debug("Calling processMoveThread dest topic is " + desttopicIdstr);
+
+ List checkbox_reminder = getRequestParamArray("moveReminder");
+ boolean checkReminder = false;
+ if (checkbox_reminder.size() != 1) {
+ // do nothing, there should be one and only one destination.
+ return gotoMain();
+ } else {
+ checkReminder = Boolean.parseBoolean((String) checkbox_reminder.get(0));
+ // reminderVal = Boolean.parseBoolean(checkReminder);
+ }
+
+ if (LOG.isDebugEnabled()) LOG.debug("Calling processMoveThread checkReminder is " + checkReminder);
+
+ Long desttopicId = Long.parseLong(desttopicIdstr);
+ DiscussionTopic desttopic = forumManager.getTopicById(desttopicId);
+ // now update topic id in mfr_message_t table, including all childrens (direct and indirect),
+ // For each move, also add a row to the mfr_move_history_t table.
+
+ Message mes = null;
+ Iterator mesiter = checkedThreads.iterator();
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread checkedThreads size = " + checkedThreads.size());
+ while (mesiter.hasNext()) {
+ Long messageId = new Long((String) mesiter.next());
+ mes = messageManager.getMessageById(messageId);
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread messageId = " + mes.getId());
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread message title = " + mes.getTitle());
+ mes.setTopic(desttopic);
+ messageManager.saveMessage(mes);
+
+ // mfr_move_history_t stores only records that are used to display reminder links. Not all moves are recorded in this
+ // table.
+ messageManager.saveMessageMoveHistory(mes.getId(), desttopicId, sourceTopicId, checkReminder);
+
+ String eventmsg = "Moving message " + mes.getId() + " from topic " + sourceTopicId + " to topic " + desttopicId;
+ EventTrackingService.post(EventTrackingService.newEvent(DiscussionForumService.EVENT_FORUMS_MOVE_THREAD, eventmsg, true));
+
+ List childrenMsg = new ArrayList(); // will store a list of child messages
+ messageManager.getChildMsgs(messageId, childrenMsg);
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread childrenMsg for " + messageId + " size = " + childrenMsg.size());
+ Iterator childiter = childrenMsg.iterator();
+
+ // update topic id for each child msg.
+ while (childiter.hasNext()) {
+ Message childMsg = (Message) childiter.next();
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread messageId = " + childMsg.getId());
+ if (LOG.isDebugEnabled()) LOG.debug("processMoveThread message title = " + childMsg.getTitle());
+ childMsg.setTopic(desttopic);
+ messageManager.saveMessage(childMsg);
+ messageManager.saveMessageMoveHistory(childMsg.getId(), desttopicId, sourceTopicId, checkReminder);
+ eventmsg = "Moving message " + childMsg.getId() + " from topic " + sourceTopicId + " to topic " + desttopicId;
+ EventTrackingService.post(EventTrackingService.newEvent(DiscussionForumService.EVENT_FORUMS_MOVE_THREAD, eventmsg, true));
+ }
+ }
+
+ setSelectedForumForCurrentTopic(desttopic);
+ selectedTopic = getDecoratedTopic(desttopic);
+ return ALL_MESSAGES;
+ }
+}
+
Index: messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/DiscussionMessageBean.java
===================================================================
--- messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/DiscussionMessageBean.java (revision 121251)
+++ messageforums-app/src/java/org/sakaiproject/tool/messageforums/ui/DiscussionMessageBean.java (working copy)
@@ -60,9 +60,10 @@
private boolean revise;
private boolean userCanDelete;
private boolean userCanEmail;
+ // Move Threads
+ private boolean selected_move;
+ private boolean moved;
-
-
private MessageForumsMessageManager messageManager;
public DiscussionMessageBean(Message msg, MessageForumsMessageManager messageManager)
@@ -71,8 +72,15 @@
this.messageManager = messageManager;
}
+ public void setMoved(boolean b) {
+ moved = b;
+ }
+ public boolean isMoved() {
+ return moved;
+ }
+
/**
* @return Returns the selected.
*/
@@ -91,7 +99,21 @@
this.selected = selected;
}
+ /**
+ * @return Returns the selected threads to move.
+ */
+ public boolean isSelected_move()
+ {
+ return selected_move;
+ }
+ /**
+ * @param selected_move: The selected threads to set.
+ */
+ public void setSelected_move(boolean selected)
+ {
+ this.selected_move = selected;
+ }
/**
* @return Returns the msg.
Index: messageforums-app/src/webapp/WEB-INF/faces-config.xml
===================================================================
--- messageforums-app/src/webapp/WEB-INF/faces-config.xml (revision 121251)
+++ messageforums-app/src/webapp/WEB-INF/faces-config.xml (working copy)
@@ -259,6 +259,11 @@
+ dfMoveThreads
+ /jsp/dfMoveThreads.jsp
+
+
+ dfViewMessage/jsp/discussionForum/message/dfViewMessage.jsp
Index: messageforums-app/src/webapp/css/msgcntr_move_thread.css
===================================================================
--- messageforums-app/src/webapp/css/msgcntr_move_thread.css (revision 0)
+++ messageforums-app/src/webapp/css/msgcntr_move_thread.css (revision 0)
@@ -0,0 +1,206 @@
+/*
+Author: Lydia Li, lydial@Stanford.edu
+Modified based on 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.
+*/
+
+.post_move_links {
+ margin-top: 10px;
+}
+
+.topic-picker{
+ display: none;
+ background-color: #FFF;
+ width: 540px;
+ padding: 6px;
+}
+
+.topic-picker form {
+ margin: 0;
+}
+
+.topic-picker .instructions {
+ font-style: italic;
+ padding-left: 0.2em;
+ margin-top: 0;
+}
+
+/* Filter Section */
+
+.topic-filter {
+ float: left;
+ top: 0;
+ display: inline;
+}
+
+.topic-num-filtered {
+}
+
+.topic-picker .header-title {
+ font-style: normal;
+ display: block;
+ font-size: 1.1em;
+ color: #000;
+}
+
+.topic-search-field {
+ margin: 0.1em 0 0.6em 0.4em;
+ width: 230px;
+}
+
+.topic-filter-fields {
+ border: 1px solid #D0D3D7;
+ padding: 0.4em 0.4em 0.4em 0.4em;
+}
+
+.topic-filter-fields label {
+ font-size: 1em !important;
+ margin-left: 0 !important;
+}
+
+/* lists */
+
+.topic-source {
+ margin-left: 10px;
+ float: left;
+ top: 0;
+ display: inline;
+}
+
+.topic-source, .topic-filter {
+ width: 318px;
+}
+
+.topic-filter, .topic-source {
+ display: block;
+ float: left;
+ clear: right;
+ position: relative;
+ margin-top: 1.5em;
+}
+
+.topic-source select {
+ font-size: 0.8em;
+}
+
+.topic-filter-header, .topic-source-picker {
+ border: 1px solid #D0D3D7;
+ padding: 1px 1px;
+ background-color: #EBECED;
+ margin-bottom: 1px;
+}
+
+.topic-filter-header {
+ padding: 3px 6px;
+}
+
+.topic-filter-header h3 {
+ margin: 0;
+ font-size: 1em;
+}
+
+.topic-filter-header .topic-header-h3 {
+ margin: 0;
+ font-size: 1em;
+ font-weight: bold;
+}
+
+.sourcetitle {
+ font-weight: bold;
+}
+
+.topic-submit {
+ clear: both;
+ display:block;
+ padding-top: 1em;
+ padding-bottom: 1em;
+ text-align: right;
+}
+
+.topic-submit input[type="text"] {
+ margin-left: 6px;
+ width: 124px;
+}
+.topic-source-footer {
+ border-top: 1px solid #D0D3D7;
+ padding: 5px 12px;
+}
+
+.topic-source-list-header {
+ border-bottom: 1px solid #D0D3D7;
+ padding: 4px 6px;
+ font-size: 1.1em;
+}
+
+.topic-source-list-header .topic-num-filtered {
+ float: right;
+}
+
+/* Scroller */
+
+.topic-picker .scroller {
+ overflow: auto;
+ overflow-x: hidden;
+ overflow-y: auto;
+ background-color: white;
+ height: 270px;
+ margin: 0;
+ padding: 0;
+}
+
+.topic-source-list div {
+ cursor: pointer;
+ padding: 6px;
+ padding-left: 0.1em;
+ border-bottom: 1px solid #999;
+}
+
+.scroller div.even {
+ background-color: #F8F9FA;
+}
+
+.jsfFormTable td {
+ border-bottom: 1px solid #CCC;
+}
+
+.topic-source-counter, .topic-source-total {
+ font-weight: bold;
+}
+
+/* Overrides for jQuery UI Dialog styles */
+
+.ui-dialog {
+ margin-top: 2em;
+}
+
+.ui-dialog .ui-dialog-title {
+ font-size: 1.1em !important;
+}
+
+.ui-dialog .ui-dialog-titlebar {
+ padding: 0.5em 0.3em 0.5em 1em !important;
+}
+
+.ui-widget-header {
+ background-image: url(../images/headerBg.jpg);
+ border: 0px;
+ background-repeat:repeat-x;
+ color: #404040;
+ font-weight: bold;
+}
+
+.threads-to-move {
+ margin-bottom: .4em;
+ margin-left: 2em;
+ font-weight: bold;
+ margin-top: .6em;
+}
+
+.checkbox-reminder{
+}
+
Property changes on: messageforums-app/src/webapp/css/msgcntr_move_thread.css
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/css/msgcntr.css.orig
===================================================================
--- messageforums-app/src/webapp/css/msgcntr.css.orig (revision 0)
+++ messageforums-app/src/webapp/css/msgcntr.css.orig (revision 0)
@@ -0,0 +1,621 @@
+/*PART 1 - FORUM AND TOPIC LISTINGS*/
+.forumHeader{
+ /*panel holding all forum metadata in forum list view*/
+ width: 99%;
+ background: #ffe;
+ border: 1px solid #fc6;
+ margin-bottom: 1em;
+}
+.forumHeader td{
+ /*inner container forum metadata in forum list view*/
+ padding: .25em .5em;
+ vertical-align: text-top;
+}
+.forumHeader td .title{
+ /*forum title in forum list view*/
+ font-weight: bold;
+ font-size: 1.3em;
+}
+.forumHeader .actionLinks,.topicBloc .actionLinks{
+ /*a spacer between title + metadata and the links*/
+ padding-left: 1em;
+}
+.topicBloc a.show,.topicBloc a.hide,.forumHeader a.show,.forumHeader a.hide,.singleMessage a.show,.singleMessage a.hide, .singleMessageReply a.show,.singleMessageReply a.hide{
+ /*show and hide descr links in various places*/
+/* color: #f55 !important;*/
+ font-size: 95%;
+ text-decoration: none !important;
+ margin: .4em 0;
+ vertical-align: middle;
+}
+.topicBloc a.show img,.topicBloc a.hide img,.forumHeader a.show img,.forumHeader a.hide img, .singleMessage a.show img,.singleMessage a.hide img, .singleMessageReply a.show img,.singleMessageReply a.hide img{
+ /*image associated with show and hide descr links in various places*/
+ vertical-align: middle;
+ padding: 2px 0;
+}
+.toggle{
+/* long description toggled container*/
+ padding-left:0;
+ font-size: .92em;
+ line-height: 1.5em;
+}
+.toggle .textPanel {
+ -ms-word-break: break-all;
+ word-break: break-all;
+}
+.topicBloc{
+ /*inner container topic metadata in forum list view, or single topic view*/
+ margin: .5em 0 1em 1em;
+ border-left: 2px solid #fc6;
+ width: 95%;
+}
+.topicBloc td{
+ padding-left: .5em;
+}
+.topicBloc td td{
+ padding-left: 0;
+}
+.topicBlocLone{
+ /*inner container topic metadata adjutments in single topic view*/
+ margin: .5em 0 1em 0;
+}
+.singleMessage .title, .singleMessageReply .title, .topicBloc .title,.pendingApproval .title{
+ /* title for all the above cases*/
+ font-weight: bold;
+ font-size: 1.2em;
+}
+.forumHeader .shortDescription,.topicBloc .shortDescription{
+ /* short description whenever one occurs - do not use padding */
+ display: block;
+ margin: .5em 0;
+ font-size:.85em;
+ -ms-word-break: break-all;
+ word-break: break-all;
+}
+.messageNew{
+ color: #000;
+ font: .84em/1em verdana, arial,sans-serif;
+ background: #fe9;
+ outline:1px solid #fff;
+ padding: 1px 2px;
+ margin-right: 5px;
+ /* display:none; - uncomment to hide the "new" message */
+}
+.childrenNew{
+ color: #e90;
+ font-size:87%;
+ font-weight:bold;
+ padding: 1px 2px;
+ margin: 5px;
+ /* display:none; - uncomment to hide the message associated with parents of new messages*/
+}
+#messNavHolder{
+ float:none;
+ display:block;
+ line-height: 2em;
+ height:100%;
+ overflow:hidden;
+ padding:2px 0;
+ margin:.3em 0;
+ clear:both;
+ }
+.jumpToNew{
+ padding:2px;
+}
+.jumpToNew a {
+ border:1px solid #ccc;
+ padding:3px;
+ padding-left:18px;
+ background: #eee url(../images/silk/arrow_down.png) center left no-repeat;
+}
+.markAllAsRead {
+ border:1px solid #ccc;
+ padding:3px;
+ padding-left:30px;
+ background: #eee url(../images/markAsRead.gif) 5px center no-repeat;
+}
+
+.topicIcon{
+/* display:none; comment to hide blue or orange speech balloons by topics in Forum list view */
+}
+/* PART 2 - TOPIC/THREAD VIEWS*/
+.messagePending{
+ background: #bf9;
+ font: .84em/1em verdana, arial,sans-serif;
+ color: #000;
+ outline:1px solid #fff;
+ padding: 1px 2px;
+ margin-right: 5px;
+}
+.messageDenied{
+ color: #000;
+ font: .84em/1em verdana, arial,sans-serif;
+ background: #f9a;
+ outline:1px solid #fff;
+ padding: 1px 2px;
+ margin-right: 5px;
+}
+.markAsReadIcon{
+ cursor: pointer;
+ margin-left:.5em;
+ background: #fff url(../images/silk/email.png) center left no-repeat
+}
+.markAsReadIcon:hover{
+ background: #fff url(../images/silk/email_open.png) center left no-repeat
+}
+.messagesFlat,.messagesThreaded{
+ width: 98% !important;
+ margin: 1em 0 !important;
+/* border-spacing: .5em;*/
+}
+.messagesFlat tbody tr div,.messagesThreaded tbody tr div{
+ border: none
+}
+.messagesFlat tbody tr td div.hierItemBlock,.messagesThreaded tbody tr td div.hierItemBlock{
+ padding: 0 0 0 .5em !important;
+ border-left: 2px solid #cd5 !important;
+ width: 95%;
+ margin: .5em !important;
+}
+.messagesThreaded tbody tr td{
+ padding-top: 0;
+ padding-bottom: 0;
+}
+.messagesThreaded tbody tr td div.hierItemBlock .textPanel{
+ clear: both;
+ width: 60%;
+ -ms-word-break: break-all;
+ word-break: break-all;
+}
+.titleBarBorder{
+ /*create an underline under message title + metadata in view body thread view*/
+ background: #fff;
+ margin: 2px 0 0 0;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child{
+ padding: 0 !important;
+ border-left: 2px solid #69d;
+ width: 100%;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child > div{
+ margin: 0;
+ border: none !important;
+}
+.messagesFlat tbody .title,.messagesThreaded tbody .title{
+ font-weight: bold;
+ font-size: 1.1em;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child .title{
+ font-weight: bold;
+ font-size: 1.2em;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child{
+ /*first message in thread in visible body view*/
+ padding: 0;
+ border-left: 2px solid #69d;
+ width: 100%;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child > div{
+ margin: 0;
+}
+.messagesThreaded tbody tr.hierItemBlock td.bogus:first-child .title{
+ /*message title of first message in thread + body view*/
+ font-weight: bold;
+ font-size: 1.2em;
+}
+.itemToolBar{
+ /* parent of all links associated with an item in thread + body view*/
+ padding-left: .5em;
+ font-size: .9em;
+}
+.itemToolBar a{
+ /* all links associated with an item in thread + body view*/
+ text-decoration: none !important;
+}
+.otherActions{
+ /*parent of all links in the "other actions" link list*/
+ padding: 0 !important;
+ color:#ccc;
+ text-align: left;
+ white-space: nowrap;
+ font-size: .95em;
+}
+.otherActions a{
+
+ }
+.allMessages .messageTitle .firstChild{
+ /*first post in a thread in topic listing (no body)*/
+ border-left: 1px solid #cd5;
+ padding-left: 5px;
+}
+.allMessages .hierItemBlock .messageTitle .firstChild{
+ /*first post in a thread in topic listing ( + body) */
+ border-left: 1px solid #69d;
+ padding-left: 5px;
+}
+
+.allMessages td.attach{
+ vertical-align:middle;
+}
+.pendingApproval{
+ /*listing of a message pending aproval*/
+ border-left: 2px solid #ccc;
+ margin: 0;
+}
+/*PART 3 - SINGLE MESSAGE DISPLAY*/
+.singleMessage{
+ /*title + metadata block of a single message display*/
+ border-left: 2px solid #ccc;
+ background:#fff;
+ margin-top: 1em;
+ padding:.3em .5em;
+}
+.singleMessageReply{
+ /*title + metadata block of a single message display*/
+ border:1px solid #ccc;
+ background:#ffe;
+ margin-top: 1em;
+ padding:.3em .5em;
+}
+table.messageActions {
+ /*single message action links top container*/
+ background: #eee;
+}
+table.messageActions td{
+ /*single message action links inner container*/
+ padding: 6px;
+ vertical-align: middle;
+ background: #eee;
+}
+.messageActions a{
+ /*single message action links*/
+ text-decoration: none !important;
+ border: 1px solid #ccc !important;
+ background: #fff;
+ padding: 2px 5px;
+ vertical-align: middle;
+ margin: 0 .3em 0 0;
+}
+.messageActions a:hover{
+ border: 1px solid #555 !important;
+ text-decoration: none !important;
+}
+#permalinkHolder{
+ border:1px solid #ccc;
+ display:none;
+ background:#ffe;
+ position:absolute;
+ padding:5px;
+}
+
+#permalinkHolder a{
+ font-weight:bold;
+ top:0;right:4px;
+ outline:none;padding:0;
+ display:block;position:absolute;
+}
+.messageActions td.otherOtherActions {
+ /*rightmost column of single message action links - not used*/
+ text-align: right;
+}
+.messageActions a img{
+ vertical-align: middle;
+}
+/*PART $ MESSAGE TYPES */
+.messageAlert{
+ list-style: none;
+ background: #FFEEEE url(../images/exclamation.gif) 5px 5px no-repeat;
+ border: 1px solid #FF5555;
+ color: #FF5555;
+ margin: .5em 0;
+ padding: 4px 0 4px 2em;
+}
+.messageInformation{
+ background: #ffe url(../images/asterisk_yellow.gif) 5px 5px no-repeat;
+ border: 1px solid #d93;
+ color: #d93;
+ margin: .5em 0;
+ padding: 4px 0 4px 2em;
+}
+.messageComfirm{
+ background: #cd5 url(../images/accept.gif) 5px 5px no-repeat;
+ border: 1px solid #790;
+ color: #779900;
+ margin: .5em 0;
+ padding: 4px 0 4px 2em;
+}
+span.messageAlert,span.messageInformation,.messageComfirm{
+ padding-right: 1em;
+}
+/*utils, special cases, sakai overrides*/
+.upLevel{
+ /*go up one level link, uncomment to show*/
+ /*display:none*/
+}
+td.attach{
+ vertical-align: middle;
+ padding: 2px 0;
+}
+td div.title{
+ font-weight: bold;
+ font-size: 1.3em;
+}
+.unreadMsg a:link,.unreadMsg a:visited{
+ color: #f55 !important;
+ text-decoration: none;
+}
+.selChanged{
+ /*flag an item as changed in organize view*/
+ border: 1px solid #aaa;
+ background: #fc6;
+ padding: 1px;
+}
+.attachPanel{
+/*panel listing attachmetns when in edit mode for forum/topic/message*/
+ border: 1px solid #eee;
+ background: #fff;
+ font-size: 1em;
+}
+.attachPanel tr:hover{
+ background: none !important;
+}
+.attachPanel th{
+ background: #ffe;
+ border-bottom: 1px solid #eee;
+ text-align: left;
+ font-weight: normal;
+ padding: .2em .5em;
+}
+.attachPanel td{
+ padding: .2em .5em;
+ vertical-align: top;
+}
+.attachPanel td.attach{
+ width: 1em;
+}
+.permissionTable {
+ width: 675px;
+}
+.permissionRoleLabel {
+ display:inline-block;
+ width:14em;
+}
+.permissionRow {
+ padding: .25em;
+ border: 1px solid #C0C0C0;
+ display:block;
+}
+.permissionPanel > table {
+ padding-top: .3em;
+}
+
+.permissionPanel td{
+ padding-left: 1em;
+ vertical-align:top;
+}
+
+.permissionPanel .checkbox {
+ display:block;
+ border: none;
+}
+
+.permissionRadioGroup {
+ border: 1px solid #aaa;
+ display:block;
+ padding:.3em;
+}
+
+.permissionRow .ui-icon { display: inline-block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; vertical-align:bottom; }
+.permissionRow .ui-icon { width: 16px; height: 16px; background-image: url(ui-lightness/images/ui-icons_222222_256x240.png); }
+.permissionRow .ui-icon-triangle-1-s { background-position: -64px -16px; }
+.permissionRow .ui-icon-triangle-1-e { background-position: -32px -16px; }
+.permissionRow .ui-accordion-content { background: #eee; border: 1px solid #ccc; }
+.permissionRow .ui-accordion-header {
+ background: #eee;
+ border-radius: 4px;
+ cursor:pointer;
+ margin-left: 4em;
+ padding: .1em .4em .1em 0;
+ border: 1px solid #ccc;
+ display:inline-block;
+ height: 1.75em;
+}
+.permissionRow .ui-accordion-header:hover {
+ border: 1px solid #666;
+}
+.permissionRow .ui-accordion-header.ui-state-active {
+ border-radius: 4px 4px 0 0;
+ position:relative;
+ top: 1px;
+ border-bottom:none;
+}
+.permissionRow .ui-accordion-header.ui-state-active:hover {
+ border-bottom:none;
+}
+
+table.discTria{
+ /*some adjustments to sakai discTria - to make whole clickable looking*/
+ border: none;
+ background: #ddd;
+ border:1px solid #ddd
+}
+table.discTria h4{
+ display: inline;
+ margin: 0;
+ padding: 3px;
+}
+table.discTria img {
+ vertical-align: middle;
+}
+table.discTria:hover{
+ cursor: pointer;
+ border:1px solid #aaa
+}
+.discTria{
+ background: transparent;
+}
+.inactive{
+ color: #aaa;
+}
+.printBlock{
+ /*top most parent of any printable block - used in print previews/pretty prints*/
+ margin: 1em;
+ padding: .5em;
+ font-size: 93%;
+ font-family: Georgia,"Times New Roman",Times,serif;
+ line-height:1.5em;
+}
+.printTable td{
+}
+
+.printBlock .textPanel a{
+
+}
+.printBlock .textPanel a:after{
+ content: " (" attr(href) ")";
+}
+
+
+.printTable td .title{
+ font-weight: bold;
+}
+.printBlock .bordered{
+ border-color: #ccc;
+}
+.printBlock .messagePending{
+ background: #ccc;
+ color: #000;
+ padding: 2px;
+ margin-right: 5px;
+ border: 1px solid #000;
+}
+.printBlock .messageDenied{
+ color: #fff;
+ background: #000;
+ padding: 3px;
+ margin-right: 5px;
+}
+.itemNav{
+ border: 1px solid #fff;
+ white-space: nowrap;
+ padding: 0 .3em;
+ background: #fff;
+}
+.itemNav:hover{
+ border: 1px solid #eee;
+ white-space: nowrap;
+ padding: 0 .3em;
+ background: #ffe;
+}
+.messageMetadata{
+ padding:1px 0;
+}
+.messageMetadata span.md{
+ border-bottom:1px solid #ccc;
+}
+a .textPanelFooter{
+ color: inherit;
+}
+
+.lastRevise{
+ margin:.3em 0;
+ padding:.3em 0;
+}
+form{margin-bottom:0;}
+
+.messageCommentWrap{
+ border:1px solid #ddd !important;
+ background: #ffd;
+ padding:3px 5px;
+ margin-bottom:10px
+}
+.messageCommentMD{
+ font-size:.9em;
+ text-decoration: underline;
+ margin-bottom:3px
+}
+.messageCommentBody{
+}
+.calWidget{
+ vertical-align:middle;
+}
+.calWidget img{
+ vertical-align:middle;
+ margin-right:3em
+}
+.authorImage {
+ float: left;
+ display: block;
+ text-align: center;
+ padding-right: .25em;
+ padding-bottom: .3em;
+}
+.listHier .authorImage img{
+ max-width: 8em;
+ max-height: 5em;
+}
+.singleMessage .authorImage img{
+ max-width: 5em;
+ max-height: 4em;
+}
+img.authorImage {
+ display: inline-block;
+ float: none;
+ max-width: 3.5em;
+ max-height: 2.5em;
+ width: auto !important;
+ vertical-align: bottom !important;
+}
+.moreMenuLink span{
+ padding-right:15px;
+ background:transparent url(../images/silk/bullet_arrow_down.png) bottom right no-repeat;
+
+}
+.moreMenu{
+ background:#fff;
+ list-style:none;
+ margin:0;
+ padding:0px;
+ border:1px solid #ccc;
+}
+.moreMenu li{
+
+}
+.moreMenu li a{
+ display:block;
+ padding:5px;
+}
+.moreMenu li a:hover{
+ background:#eee;
+ text-decoration:none;
+}
+.qtip-content{
+ max-height: 200px;
+ overflow: auto !important;
+}
+.profile2-profile{
+ width: 500px !important;
+}
+.profile2-profile-content{
+ width: 270px !important;
+}
+.createTopicsForGroupsPanel {
+ margin-left: 3em;
+ margin-top: 0.3em;
+ display: block;
+}
+.navIntraTool li span a img,.navIntraTool li {
+ vertical-align: middle;
+}
+
+#buttonBlocker,.buttonBlocker {
+ z-index:2;
+ display:none;
+ padding:0 8px;
+ background: transparent url(../images/trans.gif) top left repeat;
+ opacity:0;
+ position:absolute
+}
+#buttonBlocker:hover, .buttonBlocker:hover {
+ cursor:progress;
+}
\ No newline at end of file
Property changes on: messageforums-app/src/webapp/css/msgcntr.css.orig
___________________________________________________________________
Added: svn:executable
+ *
Index: messageforums-app/src/webapp/css/msgcntr.css
===================================================================
--- messageforums-app/src/webapp/css/msgcntr.css (revision 121251)
+++ messageforums-app/src/webapp/css/msgcntr.css (working copy)
@@ -536,6 +536,10 @@
}
.messageCommentBody{
}
+.threadMovedMsg{
+ font-size:1em;
+ color:#555555;
+}
.calWidget{
vertical-align:middle;
}
Index: messageforums-app/src/webapp/js/forum_movethread.js
===================================================================
--- messageforums-app/src/webapp/js/forum_movethread.js (revision 0)
+++ messageforums-app/src/webapp/js/forum_movethread.js (revision 0)
@@ -0,0 +1,331 @@
+/*
+Javascript 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.
+*/
+
+/*global jQuery, fluid, fluid_1_0, recipients */
+
+(function ($, fluid) {
+ var jsonData = [];
+ var forumFilterSelect = {};
+ var container = {};
+ var sourceStructure = {};
+ var sourceList = {};
+ var sourceListScroller = {};
+
+ var templates = {
+ sourceItem: '
%topiclabel
',
+ sourceItemDisabled: '
%topiclabel
'
+ };
+
+ var getJSONData = function (key) {
+ var dataSet = {};
+ for (var i = 0; i < jsonData.length; i++) {
+ if (jsonData[i].hasOwnProperty(key)) {
+ dataSet = jsonData[i];
+ }
+ }
+ return dataSet;
+ };
+
+/*
+ var stripeList = function (list) {
+ $("div.even", list).removeClass("even");
+ $("div:nth-child(even)", list).addClass("even");
+ };
+*/
+
+ var clearHighlight = function (rowElm) {
+ rowElm = (rowElm) ? $(rowElm) : $("div.highlight");
+ rowElm.removeClass("highlight");
+ };
+
+ var updateSourceCounter = function (count) {
+ count = (count !== undefined) ? count : $("div:visible",sourceList).length;
+ $(".topic-source-counter", container).text(count);
+ };
+
+ var filterList = function () {
+ var forumFilter = $(".forumDropdown").val();
+ var filteredItems = $("div", sourceList);
+ filteredItems.removeClass('shown-by-filter');
+
+ if (forumFilter === "select-forum") {
+ filteredItems.addClass('shown-by-filter');
+ filteredItems.show();
+ } else {
+ // convert the filter strings to classNames if not empty
+ forumFilter = (forumFilter === "select-forum") ? "" : "." + forumFilter;
+ // hide all rows
+ filteredItems.hide();
+ // show the rows with the right classes
+ filteredItems = $("div" + forumFilter , sourceList);
+ filteredItems.addClass('shown-by-filter');
+ filteredItems.show();
+ }
+ // restore the text filter if it's not empty
+ if ($("#searchTopic").val().replace(/^\s+|\s+$/g, '') !== "") {
+ searchByName();
+ } else {
+ updateSourceCounter(filteredItems.length);
+ }
+
+/*
+ // clear radio buttons
+
+ // disable Move Threads button
+ $(".topic-btn-save").attr("disabled", "disabled");
+*/
+ };
+
+ var saveRestorePoint = function () {
+ $("#searchTopic").val("");
+ $(".forumDropdown").val("select-forum");
+
+
+ };
+
+ var searchByName = function () {
+ var contents = $("#searchTopic").val().replace(/^\s+|\s+$/g, '').toLowerCase();
+ if (!(contents === "")) {
+ var regex = new RegExp("\\b" + contents);
+ $(".shown-by-filter", sourceList).each(function () {
+ var item = $(this);
+ var name = item.text().replace(/^\s+|\s+$/g, '').toLowerCase();
+
+ // use toggle here
+ if (name.search(regex) > -1) {
+ item.show();
+ }
+ else {
+ item.hide();
+ }
+ });
+ updateSourceCounter();
+ }
+ else {
+ filterList();
+ }
+ };
+
+ var buildForumSelect = function () {
+ var forum;
+ var forumOptionTemplate = $('#select-forum');
+ var forumOption;
+ var forums= getJSONData("forums").forums;
+ for (var i = 0; i < forums.length; i++) {
+ forum= forums[i];
+ forumOption = forumOptionTemplate.clone(true);
+ forumOption.text(forum.forumtitle);
+ forumOption.val(forum.forumid);
+ forumOption.data('forum', forums[i]);
+ forumFilterSelect.append(forumOption);
+ }
+ };
+
+ var makeSourceListItem = function (topics) {
+ var itemHTML = fluid.stringTemplate(templates.sourceItem, {
+ forumid: topics.forumid,
+ topicvalue: topics.topicid,
+ topiclabel: topics.topictitle
+ });
+ return itemHTML;
+ };
+
+ var makeSourceListItemDisabled = function (topics) {
+ var itemHTML = fluid.stringTemplate(templates.sourceItemDisabled, {
+ forumid: topics.forumid,
+ topicvalue: topics.topicid,
+ topiclabel: topics.topictitle
+ });
+ return itemHTML;
+ };
+
+ var buildThreadList = function() {
+ var idlist = [];
+ var itemHTML = "";
+ // #checkbox is the
in dfAllMessages.jsp
+ // we want to find out which messages are selected to move,
+
+ $("#checkbox input[type=checkbox]:checked").each(function() {
+ var threadid = ($(this).val());
+ idlist.push(threadid);
+ var thetitle = $(this).parent().siblings(".messageTitle").find(".messagetitlelink").text();
+ itemHTML += " - " + thetitle + " ";
+ });
+
+ $(".threads-to-move", container).html(itemHTML);
+ }
+
+ var buildSourceListScroller = function () {
+ var itemHTML = "";
+ var topics= getJSONData("topics").topics;
+ var totalTopics= topics.length;
+ for (var j = 0; j < totalTopics; j++) {
+ var sourcetitle = $(".sourcetitle").text();
+ var currtopic = topics[j].topictitle ;
+ if ( sourcetitle == currtopic) {
+ // if current topic, greyed out disable radio selection
+ itemHTML += makeSourceListItemDisabled(topics[j]);
+ }
+ else {
+ itemHTML += makeSourceListItem(topics[j]);
+ }
+ }
+ $(sourceList).html(itemHTML);
+/*
+ if (window.parent.recipientData) {
+ restoreSourceListSettings();
+ }
+*/
+ $(".topic-source-total, .topic-source-counter", container).text(totalTopics);
+ };
+
+ /* KEYBOARD NAVIGATION
+ */
+
+ var moveThreadEnabled = function (){
+ //function to check total number of CheckBoxes that are checked in a form
+ //initialize total count to zero
+ var totalChecked = 0;
+ //get total number of CheckBoxes in form
+ if (typeof document.forms['msgForum'].moveCheckbox.length === 'undefined') {
+ /*when there is just one checkbox moveCheckbox is not an array, document.forms['msgForum'].moveCheckbox.length returns undefined */
+ if (document.forms['msgForum'].moveCheckbox.checked == true )
+ {
+ totalChecked += 1;
+ }
+ }
+ else {
+ // more than one checkbox is checked.
+ var chkBoxCount = document.forms['msgForum'].moveCheckbox.length;
+ //loop through each CheckBox
+ for (var i = 0; i < chkBoxCount; i++)
+ {
+ //check the state of each CheckBox
+ if (eval("document.forms['msgForum'].moveCheckbox[" + i + "].checked") == true)
+ {
+ //it's checked so increment the counter
+ totalChecked += 1;
+ }
+ }
+ }
+ if (totalChecked >0){
+ // enable the move link
+ return true;
+ }
+ else {
+ return false;
+ }
+
+ }
+
+ /* SETUP DOM EVENTS */
+
+ var bindDOMelements = function () {
+
+ $(".topic-btn-save").click(function () {
+ // call processMoveThread
+ var linkid = "msgForum:hidden_move_message_commandLink";
+ var hiddenmovelink = document.getElementById(linkid);
+ hiddenmovelink.onclick();
+// $(".topic-picker").dialog("close");
+ });
+
+ $("#searchTopic").keyup(function (event) {
+ if (event.keyCode === $.ui.keyCode.ESCAPE || $(this).val() === "") {
+ //if esc is pressed we want to clear the value of search box
+ $(this).val("");
+ filterList();
+ } else {
+ searchByName();
+ }
+ });
+
+ $(".checkbox-reminder").click(function () {
+ var reminder = $("#checkbox-reminder:checked").val();
+ if (reminder != undefined) {
+ // reminder checkbox is checked
+ $(".moveReminder").val(true);
+ }
+ else {
+ // reminder checkbox is NOT checked
+ $(".moveReminder").val(false);
+ }
+ });
+
+ $(".topic-btn-cancel").click(function () {
+ $(".topic-picker").dialog("close");
+ });
+
+ $('input:radio',sourceList).click(function () {
+ var radiovalue = $(this).val();
+ // enable Move Threads button
+ $(".topic-btn-save").removeAttr("disabled");
+ var topichidden = $(".selectedTopicid");
+ $(".selectedTopicid").val(radiovalue);
+ });
+
+ $(sourceList).click(function (event) {
+ var elm = ($(event.target).is('div')) ? $(event.target) : $(event.target).parent("div");
+ // addSingleRow(elm);
+ });
+
+ $(".forumDropdown").change(function () {
+ filterList();
+ });
+
+ $(".display-topic-picker").click(function () {
+ if (moveThreadEnabled()) {
+ buildThreadList();
+ //buildSourceListScroller();
+ $(".topic-picker").dialog("open");
+ //saveRestorePoint();
+ }
+ else {
+ // do nothing
+ }
+ });
+
+ };
+
+ /* Initialization Routine */
+
+ var parseData = function () {
+ var txtData = $("#data").text();
+ jsonData = JSON.parse(txtData, null);
+ };
+
+ var collectElements = function () {
+ container = $("#topic-picker");
+ forumFilterSelect = $(".forumDropdown", container);
+ sourceList = $(".topic-source-list", container);
+ sourceStructure = $(".topic-source", container);
+ };
+
+ var initDialog = function () {
+ $.ui.dialog.defaults.bgiframe = true;
+ $(".topic-picker").dialog({ autoOpen: false, width: 660, modal: true, position: 'top'});
+ };
+
+ $(function () {
+ window.parent.scrollTo(0,0);
+ parseData();
+ collectElements();
+ initDialog();
+ sourceListScroller = fluid.scroller($(".topic-source-scroller-inner"), { maxHeight: 270});
+ //buildThreadList();
+ buildSourceListScroller();
+ buildForumSelect();
+ $(".forumDropdown").val("select-forum");
+ filterList();
+ bindDOMelements();
+ // stripeList(sourceList);
+ });
+
+})(jQuery, fluid_1_0);
Property changes on: messageforums-app/src/webapp/js/forum_movethread.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/ui.dialog.js
===================================================================
--- messageforums-app/src/webapp/js/ui.dialog.js (revision 0)
+++ messageforums-app/src/webapp/js/ui.dialog.js (revision 0)
@@ -0,0 +1,650 @@
+/*
+ * jQuery UI Dialog 1.7
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Dialog
+ *
+ * Depends:
+ * ui.core.js
+ * ui.draggable.js
+ * ui.resizable.js
+ */
+(function($) {
+
+var setDataSwitch = {
+ dragStart: "start.draggable",
+ drag: "drag.draggable",
+ dragStop: "stop.draggable",
+ maxHeight: "maxHeight.resizable",
+ minHeight: "minHeight.resizable",
+ maxWidth: "maxWidth.resizable",
+ minWidth: "minWidth.resizable",
+ resizeStart: "start.resizable",
+ resize: "drag.resizable",
+ resizeStop: "stop.resizable"
+ },
+
+ uiDialogClasses =
+ 'ui-dialog ' +
+ 'ui-widget ' +
+ 'ui-widget-content ' +
+ 'ui-corner-all ';
+
+$.widget("ui.dialog", {
+
+ _init: function() {
+ this.originalTitle = this.element.attr('title');
+
+ var self = this,
+ options = this.options,
+
+ title = options.title || this.originalTitle || ' ',
+ titleId = $.ui.dialog.getTitleId(this.element),
+
+ uiDialog = (this.uiDialog = $(''))
+ .appendTo(document.body)
+ .hide()
+ .addClass(uiDialogClasses + options.dialogClass)
+ .css({
+ position: 'absolute',
+ overflow: 'hidden',
+ zIndex: options.zIndex
+ })
+ // setting tabIndex makes the div focusable
+ // setting outline to 0 prevents a border on focus in Mozilla
+ .attr('tabIndex', -1).css('outline', 0).keydown(function(event) {
+ (options.closeOnEscape && event.keyCode
+ && event.keyCode == $.ui.keyCode.ESCAPE && self.close(event));
+ })
+ .attr({
+ role: 'dialog',
+ 'aria-labelledby': titleId
+ })
+ .mousedown(function(event) {
+ self.moveToTop(false, event);
+ }),
+
+ uiDialogContent = this.element
+ .show()
+ .removeAttr('title')
+ .addClass(
+ 'ui-dialog-content ' +
+ 'ui-widget-content')
+ .appendTo(uiDialog),
+
+ uiDialogTitlebar = (this.uiDialogTitlebar = $(''))
+ .addClass(
+ 'ui-dialog-titlebar ' +
+ 'ui-widget-header ' +
+ 'ui-corner-all ' +
+ 'ui-helper-clearfix'
+ )
+ .prependTo(uiDialog),
+
+ uiDialogTitlebarClose = $('')
+ .addClass(
+ 'ui-dialog-titlebar-close ' +
+ 'ui-corner-all'
+ )
+ .attr('role', 'button')
+ .hover(
+ function() {
+ uiDialogTitlebarClose.addClass('ui-state-hover');
+ },
+ function() {
+ uiDialogTitlebarClose.removeClass('ui-state-hover');
+ }
+ )
+ .focus(function() {
+ uiDialogTitlebarClose.addClass('ui-state-focus');
+ })
+ .blur(function() {
+ uiDialogTitlebarClose.removeClass('ui-state-focus');
+ })
+ .mousedown(function(ev) {
+ ev.stopPropagation();
+ })
+ .click(function(event) {
+ self.close(event);
+ return false;
+ })
+ .appendTo(uiDialogTitlebar),
+
+ uiDialogTitlebarCloseText = (this.uiDialogTitlebarCloseText = $(''))
+ .addClass(
+ 'ui-icon ' +
+ 'ui-icon-closethick'
+ )
+ .text(options.closeText)
+ .appendTo(uiDialogTitlebarClose),
+
+ uiDialogTitle = $('')
+ .addClass('ui-dialog-title')
+ .attr('id', titleId)
+ .html(title)
+ .prependTo(uiDialogTitlebar);
+
+ uiDialogTitlebar.find("*").add(uiDialogTitlebar).disableSelection();
+
+ (options.draggable && $.fn.draggable && this._makeDraggable());
+ (options.resizable && $.fn.resizable && this._makeResizable());
+
+ this._createButtons(options.buttons);
+ this._isOpen = false;
+
+ (options.bgiframe && $.fn.bgiframe && uiDialog.bgiframe());
+ (options.autoOpen && this.open());
+
+ },
+
+ destroy: function() {
+ (this.overlay && this.overlay.destroy());
+ this.uiDialog.hide();
+ this.element
+ .unbind('.dialog')
+ .removeData('dialog')
+ .removeClass('ui-dialog-content ui-widget-content')
+ .hide().appendTo('body');
+ this.uiDialog.remove();
+
+ (this.originalTitle && this.element.attr('title', this.originalTitle));
+ },
+
+ close: function(event) {
+ var self = this;
+
+ if (false === self._trigger('beforeclose', event)) {
+ return;
+ }
+
+ (self.overlay && self.overlay.destroy());
+ self.uiDialog.unbind('keypress.ui-dialog');
+
+ (self.options.hide
+ ? self.uiDialog.hide(self.options.hide, function() {
+ self._trigger('close', event);
+ })
+ : self.uiDialog.hide() && self._trigger('close', event));
+
+ $.ui.dialog.overlay.resize();
+
+ self._isOpen = false;
+ },
+
+ isOpen: function() {
+ return this._isOpen;
+ },
+
+ // the force parameter allows us to move modal dialogs to their correct
+ // position on open
+ moveToTop: function(force, event) {
+
+ if ((this.options.modal && !force)
+ || (!this.options.stack && !this.options.modal)) {
+ return this._trigger('focus', event);
+ }
+
+ if (this.options.zIndex > $.ui.dialog.maxZ) {
+ $.ui.dialog.maxZ = this.options.zIndex;
+ }
+ (this.overlay && this.overlay.$el.css('z-index', $.ui.dialog.overlay.maxZ = ++$.ui.dialog.maxZ));
+
+ //Save and then restore scroll since Opera 9.5+ resets when parent z-Index is changed.
+ // http://ui.jquery.com/bugs/ticket/3193
+ var saveScroll = { scrollTop: this.element.attr('scrollTop'), scrollLeft: this.element.attr('scrollLeft') };
+ this.uiDialog.css('z-index', ++$.ui.dialog.maxZ);
+ this.element.attr(saveScroll);
+ this._trigger('focus', event);
+ },
+
+ open: function() {
+ if (this._isOpen) { return; }
+
+ var options = this.options,
+ uiDialog = this.uiDialog;
+
+ this.overlay = options.modal ? new $.ui.dialog.overlay(this) : null;
+ (uiDialog.next().length && uiDialog.appendTo('body'));
+ this._size();
+ this._position(options.position);
+ uiDialog.show(options.show);
+ this.moveToTop(true);
+
+ // prevent tabbing out of modal dialogs
+ (options.modal && uiDialog.bind('keypress.ui-dialog', function(event) {
+ if (event.keyCode != $.ui.keyCode.TAB) {
+ return;
+ }
+
+ var tabbables = $(':tabbable', this),
+ first = tabbables.filter(':first')[0],
+ last = tabbables.filter(':last')[0];
+
+ if (event.target == last && !event.shiftKey) {
+ setTimeout(function() {
+ first.focus();
+ }, 1);
+ } else if (event.target == first && event.shiftKey) {
+ setTimeout(function() {
+ last.focus();
+ }, 1);
+ }
+ }));
+
+ // set focus to the first tabbable element in the content area or the first button
+ // if there are no tabbable elements, set focus on the dialog itself
+ $([])
+ .add(uiDialog.find('.ui-dialog-content :tabbable:first'))
+ .add(uiDialog.find('.ui-dialog-buttonpane :tabbable:first'))
+ .add(uiDialog)
+ .filter(':first')
+ .focus();
+
+ this._trigger('open');
+ this._isOpen = true;
+ },
+
+ _createButtons: function(buttons) {
+ var self = this,
+ hasButtons = false,
+ uiDialogButtonPane = $('')
+ .addClass(
+ 'ui-dialog-buttonpane ' +
+ 'ui-widget-content ' +
+ 'ui-helper-clearfix'
+ );
+
+ // if we already have a button pane, remove it
+ this.uiDialog.find('.ui-dialog-buttonpane').remove();
+
+ (typeof buttons == 'object' && buttons !== null &&
+ $.each(buttons, function() { return !(hasButtons = true); }));
+ if (hasButtons) {
+ $.each(buttons, function(name, fn) {
+ $('')
+ .addClass(
+ 'ui-state-default ' +
+ 'ui-corner-all'
+ )
+ .text(name)
+ .click(function() { fn.apply(self.element[0], arguments); })
+ .hover(
+ function() {
+ $(this).addClass('ui-state-hover');
+ },
+ function() {
+ $(this).removeClass('ui-state-hover');
+ }
+ )
+ .focus(function() {
+ $(this).addClass('ui-state-focus');
+ })
+ .blur(function() {
+ $(this).removeClass('ui-state-focus');
+ })
+ .appendTo(uiDialogButtonPane);
+ });
+ uiDialogButtonPane.appendTo(this.uiDialog);
+ }
+ },
+
+ _makeDraggable: function() {
+ var self = this,
+ options = this.options,
+ heightBeforeDrag;
+
+ this.uiDialog.draggable({
+ cancel: '.ui-dialog-content',
+ handle: '.ui-dialog-titlebar',
+ containment: 'document',
+ start: function() {
+ heightBeforeDrag = options.height;
+ $(this).height($(this).height()).addClass("ui-dialog-dragging");
+ (options.dragStart && options.dragStart.apply(self.element[0], arguments));
+ },
+ drag: function() {
+ (options.drag && options.drag.apply(self.element[0], arguments));
+ },
+ stop: function() {
+ $(this).removeClass("ui-dialog-dragging").height(heightBeforeDrag);
+ (options.dragStop && options.dragStop.apply(self.element[0], arguments));
+ $.ui.dialog.overlay.resize();
+ }
+ });
+ },
+
+ _makeResizable: function(handles) {
+ handles = (handles === undefined ? this.options.resizable : handles);
+ var self = this,
+ options = this.options,
+ resizeHandles = typeof handles == 'string'
+ ? handles
+ : 'n,e,s,w,se,sw,ne,nw';
+
+ this.uiDialog.resizable({
+ cancel: '.ui-dialog-content',
+ alsoResize: this.element,
+ maxWidth: options.maxWidth,
+ maxHeight: options.maxHeight,
+ minWidth: options.minWidth,
+ minHeight: options.minHeight,
+ start: function() {
+ $(this).addClass("ui-dialog-resizing");
+ (options.resizeStart && options.resizeStart.apply(self.element[0], arguments));
+ },
+ resize: function() {
+ (options.resize && options.resize.apply(self.element[0], arguments));
+ },
+ handles: resizeHandles,
+ stop: function() {
+ $(this).removeClass("ui-dialog-resizing");
+ options.height = $(this).height();
+ options.width = $(this).width();
+ (options.resizeStop && options.resizeStop.apply(self.element[0], arguments));
+ $.ui.dialog.overlay.resize();
+ }
+ })
+ .find('.ui-resizable-se').addClass('ui-icon ui-icon-grip-diagonal-se');
+ },
+
+ _position: function(pos) {
+ var wnd = $(window), doc = $(document),
+ pTop = doc.scrollTop(), pLeft = doc.scrollLeft(),
+ minTop = pTop;
+
+ if ($.inArray(pos, ['center','top','right','bottom','left']) >= 0) {
+ pos = [
+ pos == 'right' || pos == 'left' ? pos : 'center',
+ pos == 'top' || pos == 'bottom' ? pos : 'middle'
+ ];
+ }
+ if (pos.constructor != Array) {
+ pos = ['center', 'middle'];
+ }
+ if (pos[0].constructor == Number) {
+ pLeft += pos[0];
+ } else {
+ switch (pos[0]) {
+ case 'left':
+ pLeft += 0;
+ break;
+ case 'right':
+ pLeft += wnd.width() - this.uiDialog.outerWidth();
+ break;
+ default:
+ case 'center':
+ pLeft += (wnd.width() - this.uiDialog.outerWidth()) / 2;
+ }
+ }
+ if (pos[1].constructor == Number) {
+ pTop += pos[1];
+ } else {
+ switch (pos[1]) {
+ case 'top':
+ pTop += 0;
+ break;
+ case 'bottom':
+ pTop += wnd.height() - this.uiDialog.outerHeight();
+ break;
+ default:
+ case 'middle':
+ pTop += (wnd.height() - this.uiDialog.outerHeight()) / 2;
+ }
+ }
+
+ // prevent the dialog from being too high (make sure the titlebar
+ // is accessible)
+ pTop = Math.max(pTop, minTop);
+ this.uiDialog.css({top: pTop, left: pLeft});
+ },
+
+ _setData: function(key, value){
+ (setDataSwitch[key] && this.uiDialog.data(setDataSwitch[key], value));
+ switch (key) {
+ case "buttons":
+ this._createButtons(value);
+ break;
+ case "closeText":
+ this.uiDialogTitlebarCloseText.text(value);
+ break;
+ case "dialogClass":
+ this.uiDialog
+ .removeClass(this.options.dialogClass)
+ .addClass(uiDialogClasses + value);
+ break;
+ case "draggable":
+ (value
+ ? this._makeDraggable()
+ : this.uiDialog.draggable('destroy'));
+ break;
+ case "height":
+ this.uiDialog.height(value);
+ break;
+ case "position":
+ this._position(value);
+ break;
+ case "resizable":
+ var uiDialog = this.uiDialog,
+ isResizable = this.uiDialog.is(':data(resizable)');
+
+ // currently resizable, becoming non-resizable
+ (isResizable && !value && uiDialog.resizable('destroy'));
+
+ // currently resizable, changing handles
+ (isResizable && typeof value == 'string' &&
+ uiDialog.resizable('option', 'handles', value));
+
+ // currently non-resizable, becoming resizable
+ (isResizable || this._makeResizable(value));
+ break;
+ case "title":
+ $(".ui-dialog-title", this.uiDialogTitlebar).html(value || ' ');
+ break;
+ case "width":
+ this.uiDialog.width(value);
+ break;
+ }
+
+ $.widget.prototype._setData.apply(this, arguments);
+ },
+
+ _size: function() {
+ /* If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
+ * divs will both have width and height set, so we need to reset them
+ */
+ var options = this.options;
+
+ // reset content sizing
+ this.element.css({
+ height: 0,
+ minHeight: 0,
+ width: 'auto'
+ });
+
+ // reset wrapper sizing
+ // determine the height of all the non-content elements
+ var nonContentHeight = this.uiDialog.css({
+ height: 'auto',
+ width: options.width
+ })
+ .height();
+
+ this.element
+ .css({
+ minHeight: Math.max(options.minHeight - nonContentHeight, 0),
+ height: options.height == 'auto'
+ ? 'auto'
+ : Math.max(options.height - nonContentHeight, 0)
+ });
+ }
+});
+
+$.extend($.ui.dialog, {
+ version: "1.7",
+ defaults: {
+ autoOpen: true,
+ bgiframe: false,
+ buttons: {},
+ closeOnEscape: true,
+ closeText: 'close',
+ dialogClass: '',
+ draggable: true,
+ hide: null,
+ height: 'auto',
+ maxHeight: false,
+ maxWidth: false,
+ minHeight: 150,
+ minWidth: 150,
+ modal: false,
+ position: 'center',
+ resizable: true,
+ show: null,
+ stack: true,
+ title: '',
+ width: 300,
+ zIndex: 1000
+ },
+
+ getter: 'isOpen',
+
+ uuid: 0,
+ maxZ: 0,
+
+ getTitleId: function($el) {
+ return 'ui-dialog-title-' + ($el.attr('id') || ++this.uuid);
+ },
+
+ overlay: function(dialog) {
+ this.$el = $.ui.dialog.overlay.create(dialog);
+ }
+});
+
+$.extend($.ui.dialog.overlay, {
+ instances: [],
+ maxZ: 0,
+ events: $.map('focus,mousedown,mouseup,keydown,keypress,click'.split(','),
+ function(event) { return event + '.dialog-overlay'; }).join(' '),
+ create: function(dialog) {
+ if (this.instances.length === 0) {
+ // prevent use of anchors and inputs
+ // we use a setTimeout in case the overlay is created from an
+ // event that we're going to be cancelling (see #2804)
+ setTimeout(function() {
+ $(document).bind($.ui.dialog.overlay.events, function(event) {
+ var dialogZ = $(event.target).parents('.ui-dialog').css('zIndex') || 0;
+ return (dialogZ > $.ui.dialog.overlay.maxZ);
+ });
+ }, 1);
+
+ // allow closing by pressing the escape key
+ $(document).bind('keydown.dialog-overlay', function(event) {
+ (dialog.options.closeOnEscape && event.keyCode
+ && event.keyCode == $.ui.keyCode.ESCAPE && dialog.close(event));
+ });
+
+ // handle window resize
+ $(window).bind('resize.dialog-overlay', $.ui.dialog.overlay.resize);
+ }
+
+ var $el = $('').appendTo(document.body)
+ .addClass('ui-widget-overlay').css({
+ width: this.width(),
+ height: this.height()
+ });
+
+ (dialog.options.bgiframe && $.fn.bgiframe && $el.bgiframe());
+
+ this.instances.push($el);
+ return $el;
+ },
+
+ destroy: function($el) {
+ this.instances.splice($.inArray(this.instances, $el), 1);
+
+ if (this.instances.length === 0) {
+ $([document, window]).unbind('.dialog-overlay');
+ }
+
+ $el.remove();
+ },
+
+ height: function() {
+ // handle IE 6
+ if ($.browser.msie && $.browser.version < 7) {
+ var scrollHeight = Math.max(
+ document.documentElement.scrollHeight,
+ document.body.scrollHeight
+ );
+ var offsetHeight = Math.max(
+ document.documentElement.offsetHeight,
+ document.body.offsetHeight
+ );
+
+ if (scrollHeight < offsetHeight) {
+ return $(window).height() + 'px';
+ } else {
+ return scrollHeight + 'px';
+ }
+ // handle "good" browsers
+ } else {
+ return $(document).height() + 'px';
+ }
+ },
+
+ width: function() {
+ // handle IE 6
+ if ($.browser.msie && $.browser.version < 7) {
+ var scrollWidth = Math.max(
+ document.documentElement.scrollWidth,
+ document.body.scrollWidth
+ );
+ var offsetWidth = Math.max(
+ document.documentElement.offsetWidth,
+ document.body.offsetWidth
+ );
+
+ if (scrollWidth < offsetWidth) {
+ return $(window).width() + 'px';
+ } else {
+ return scrollWidth + 'px';
+ }
+ // handle "good" browsers
+ } else {
+ return $(document).width() + 'px';
+ }
+ },
+
+ resize: function() {
+ /* If the dialog is draggable and the user drags it past the
+ * right edge of the window, the document becomes wider so we
+ * need to stretch the overlay. If the user then drags the
+ * dialog back to the left, the document will become narrower,
+ * so we need to shrink the overlay to the appropriate size.
+ * This is handled by shrinking the overlay before setting it
+ * to the full document size.
+ */
+ var $overlays = $([]);
+ $.each($.ui.dialog.overlay.instances, function() {
+ $overlays = $overlays.add(this);
+ });
+
+ $overlays.css({
+ width: 0,
+ height: 0
+ }).css({
+ width: $.ui.dialog.overlay.width(),
+ height: $.ui.dialog.overlay.height()
+ });
+ }
+});
+
+$.extend($.ui.dialog.overlay.prototype, {
+ destroy: function() {
+ $.ui.dialog.overlay.destroy(this.$el);
+ }
+});
+
+})(jQuery);
Property changes on: messageforums-app/src/webapp/js/ui.dialog.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/ui.draggable.js
===================================================================
--- messageforums-app/src/webapp/js/ui.draggable.js (revision 0)
+++ messageforums-app/src/webapp/js/ui.draggable.js (revision 0)
@@ -0,0 +1,766 @@
+/*
+ * jQuery UI Draggable 1.7
+ *
+ * Copyright (c) 2009 AUTHORS.txt (http://jqueryui.com/about)
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * and GPL (GPL-LICENSE.txt) licenses.
+ *
+ * http://docs.jquery.com/UI/Draggables
+ *
+ * Depends:
+ * ui.core.js
+ */
+(function($) {
+
+$.widget("ui.draggable", $.extend({}, $.ui.mouse, {
+
+ _init: function() {
+
+ if (this.options.helper == 'original' && !(/^(?:r|a|f)/).test(this.element.css("position")))
+ this.element[0].style.position = 'relative';
+
+ (this.options.addClasses && this.element.addClass("ui-draggable"));
+ (this.options.disabled && this.element.addClass("ui-draggable-disabled"));
+
+ this._mouseInit();
+
+ },
+
+ destroy: function() {
+ if(!this.element.data('draggable')) return;
+ this.element
+ .removeData("draggable")
+ .unbind(".draggable")
+ .removeClass("ui-draggable"
+ + " ui-draggable-dragging"
+ + " ui-draggable-disabled");
+ this._mouseDestroy();
+ },
+
+ _mouseCapture: function(event) {
+
+ var o = this.options;
+
+ if (this.helper || o.disabled || $(event.target).is('.ui-resizable-handle'))
+ return false;
+
+ //Quit if we're not on a valid handle
+ this.handle = this._getHandle(event);
+ if (!this.handle)
+ return false;
+
+ return true;
+
+ },
+
+ _mouseStart: function(event) {
+
+ var o = this.options;
+
+ //Create and append the visible helper
+ this.helper = this._createHelper(event);
+
+ //Cache the helper size
+ this._cacheHelperProportions();
+
+ //If ddmanager is used for droppables, set the global draggable
+ if($.ui.ddmanager)
+ $.ui.ddmanager.current = this;
+
+ /*
+ * - Position generation -
+ * This block generates everything position related - it's the core of draggables.
+ */
+
+ //Cache the margins of the original element
+ this._cacheMargins();
+
+ //Store the helper's css position
+ this.cssPosition = this.helper.css("position");
+ this.scrollParent = this.helper.scrollParent();
+
+ //The element's absolute position on the page minus margins
+ this.offset = this.element.offset();
+ this.offset = {
+ top: this.offset.top - this.margins.top,
+ left: this.offset.left - this.margins.left
+ };
+
+ $.extend(this.offset, {
+ click: { //Where the click happened, relative to the element
+ left: event.pageX - this.offset.left,
+ top: event.pageY - this.offset.top
+ },
+ parent: this._getParentOffset(),
+ relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
+ });
+
+ //Generate the original position
+ this.originalPosition = this._generatePosition(event);
+ this.originalPageX = event.pageX;
+ this.originalPageY = event.pageY;
+
+ //Adjust the mouse offset relative to the helper if 'cursorAt' is supplied
+ if(o.cursorAt)
+ this._adjustOffsetFromHelper(o.cursorAt);
+
+ //Set a containment if given in the options
+ if(o.containment)
+ this._setContainment();
+
+ //Call plugins and callbacks
+ this._trigger("start", event);
+
+ //Recache the helper size
+ this._cacheHelperProportions();
+
+ //Prepare the droppable offsets
+ if ($.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(this, event);
+
+ this.helper.addClass("ui-draggable-dragging");
+ this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
+ return true;
+ },
+
+ _mouseDrag: function(event, noPropagation) {
+
+ //Compute the helpers position
+ this.position = this._generatePosition(event);
+ this.positionAbs = this._convertPositionTo("absolute");
+
+ //Call plugins and callbacks and use the resulting position if something is returned
+ if (!noPropagation) {
+ var ui = this._uiHash();
+ this._trigger('drag', event, ui);
+ this.position = ui.position;
+ }
+
+ if(!this.options.axis || this.options.axis != "y") this.helper[0].style.left = this.position.left+'px';
+ if(!this.options.axis || this.options.axis != "x") this.helper[0].style.top = this.position.top+'px';
+ if($.ui.ddmanager) $.ui.ddmanager.drag(this, event);
+
+ return false;
+ },
+
+ _mouseStop: function(event) {
+
+ //If we are using droppables, inform the manager about the drop
+ var dropped = false;
+ if ($.ui.ddmanager && !this.options.dropBehaviour)
+ dropped = $.ui.ddmanager.drop(this, event);
+
+ //if a drop comes from outside (a sortable)
+ if(this.dropped) {
+ dropped = this.dropped;
+ this.dropped = false;
+ }
+
+ if((this.options.revert == "invalid" && !dropped) || (this.options.revert == "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
+ var self = this;
+ $(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
+ self._trigger("stop", event);
+ self._clear();
+ });
+ } else {
+ this._trigger("stop", event);
+ this._clear();
+ }
+
+ return false;
+ },
+
+ _getHandle: function(event) {
+
+ var handle = !this.options.handle || !$(this.options.handle, this.element).length ? true : false;
+ $(this.options.handle, this.element)
+ .find("*")
+ .andSelf()
+ .each(function() {
+ if(this == event.target) handle = true;
+ });
+
+ return handle;
+
+ },
+
+ _createHelper: function(event) {
+
+ var o = this.options;
+ var helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper == 'clone' ? this.element.clone() : this.element);
+
+ if(!helper.parents('body').length)
+ helper.appendTo((o.appendTo == 'parent' ? this.element[0].parentNode : o.appendTo));
+
+ if(helper[0] != this.element[0] && !(/(fixed|absolute)/).test(helper.css("position")))
+ helper.css("position", "absolute");
+
+ return helper;
+
+ },
+
+ _adjustOffsetFromHelper: function(obj) {
+ if(obj.left != undefined) this.offset.click.left = obj.left + this.margins.left;
+ if(obj.right != undefined) this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
+ if(obj.top != undefined) this.offset.click.top = obj.top + this.margins.top;
+ if(obj.bottom != undefined) this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
+ },
+
+ _getParentOffset: function() {
+
+ //Get the offsetParent and cache its position
+ this.offsetParent = this.helper.offsetParent();
+ var po = this.offsetParent.offset();
+
+ // This is a special case where we need to modify a offset calculated on start, since the following happened:
+ // 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
+ // 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
+ // the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
+ if(this.cssPosition == 'absolute' && this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) {
+ po.left += this.scrollParent.scrollLeft();
+ po.top += this.scrollParent.scrollTop();
+ }
+
+ if((this.offsetParent[0] == document.body) //This needs to be actually done for all browsers, since pageX/pageY includes this information
+ || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() == 'html' && $.browser.msie)) //Ugly IE fix
+ po = { top: 0, left: 0 };
+
+ return {
+ top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
+ left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
+ };
+
+ },
+
+ _getRelativeOffset: function() {
+
+ if(this.cssPosition == "relative") {
+ var p = this.element.position();
+ return {
+ top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
+ left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
+ };
+ } else {
+ return { top: 0, left: 0 };
+ }
+
+ },
+
+ _cacheMargins: function() {
+ this.margins = {
+ left: (parseInt(this.element.css("marginLeft"),10) || 0),
+ top: (parseInt(this.element.css("marginTop"),10) || 0)
+ };
+ },
+
+ _cacheHelperProportions: function() {
+ this.helperProportions = {
+ width: this.helper.outerWidth(),
+ height: this.helper.outerHeight()
+ };
+ },
+
+ _setContainment: function() {
+
+ var o = this.options;
+ if(o.containment == 'parent') o.containment = this.helper[0].parentNode;
+ if(o.containment == 'document' || o.containment == 'window') this.containment = [
+ 0 - this.offset.relative.left - this.offset.parent.left,
+ 0 - this.offset.relative.top - this.offset.parent.top,
+ $(o.containment == 'document' ? document : window).width() - this.helperProportions.width - this.margins.left,
+ ($(o.containment == 'document' ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
+ ];
+
+ if(!(/^(document|window|parent)$/).test(o.containment) && o.containment.constructor != Array) {
+ var ce = $(o.containment)[0]; if(!ce) return;
+ var co = $(o.containment).offset();
+ var over = ($(ce).css("overflow") != 'hidden');
+
+ this.containment = [
+ co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
+ co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
+ co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
+ co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
+ ];
+ } else if(o.containment.constructor == Array) {
+ this.containment = o.containment;
+ }
+
+ },
+
+ _convertPositionTo: function(d, pos) {
+
+ if(!pos) pos = this.position;
+ var mod = d == "absolute" ? 1 : -1;
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ return {
+ top: (
+ pos.top // The absolute mouse position
+ + this.offset.relative.top * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.top * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
+ ),
+ left: (
+ pos.left // The absolute mouse position
+ + this.offset.relative.left * mod // Only for relative positioned nodes: Relative offset from element to offset parent
+ + this.offset.parent.left * mod // The offsetParent's offset without borders (offset + border)
+ - ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
+ )
+ };
+
+ },
+
+ _generatePosition: function(event) {
+
+ var o = this.options, scroll = this.cssPosition == 'absolute' && !(this.scrollParent[0] != document && $.ui.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
+
+ // This is another very weird special case that only happens for relative elements:
+ // 1. If the css position is relative
+ // 2. and the scroll parent is the document or similar to the offset parent
+ // we have to refresh the relative offset during the scroll so there are no jumps
+ if(this.cssPosition == 'relative' && !(this.scrollParent[0] != document && this.scrollParent[0] != this.offsetParent[0])) {
+ this.offset.relative = this._getRelativeOffset();
+ }
+
+ var pageX = event.pageX;
+ var pageY = event.pageY;
+
+ /*
+ * - Position constraining -
+ * Constrain the position to a mix of grid, containment.
+ */
+
+ if(this.originalPosition) { //If we are not dragging yet, we won't check for options
+
+ if(this.containment) {
+ if(event.pageX - this.offset.click.left < this.containment[0]) pageX = this.containment[0] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top < this.containment[1]) pageY = this.containment[1] + this.offset.click.top;
+ if(event.pageX - this.offset.click.left > this.containment[2]) pageX = this.containment[2] + this.offset.click.left;
+ if(event.pageY - this.offset.click.top > this.containment[3]) pageY = this.containment[3] + this.offset.click.top;
+ }
+
+ if(o.grid) {
+ var top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
+ pageY = this.containment ? (!(top - this.offset.click.top < this.containment[1] || top - this.offset.click.top > this.containment[3]) ? top : (!(top - this.offset.click.top < this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
+
+ var left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
+ pageX = this.containment ? (!(left - this.offset.click.left < this.containment[0] || left - this.offset.click.left > this.containment[2]) ? left : (!(left - this.offset.click.left < this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
+ }
+
+ }
+
+ return {
+ top: (
+ pageY // The absolute mouse position
+ - this.offset.click.top // Click offset (relative to the element)
+ - this.offset.relative.top // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.top // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
+ ),
+ left: (
+ pageX // The absolute mouse position
+ - this.offset.click.left // Click offset (relative to the element)
+ - this.offset.relative.left // Only for relative positioned nodes: Relative offset from element to offset parent
+ - this.offset.parent.left // The offsetParent's offset without borders (offset + border)
+ + ($.browser.safari && this.cssPosition == 'fixed' ? 0 : ( this.cssPosition == 'fixed' ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
+ )
+ };
+
+ },
+
+ _clear: function() {
+ this.helper.removeClass("ui-draggable-dragging");
+ if(this.helper[0] != this.element[0] && !this.cancelHelperRemoval) this.helper.remove();
+ //if($.ui.ddmanager) $.ui.ddmanager.current = null;
+ this.helper = null;
+ this.cancelHelperRemoval = false;
+ },
+
+ // From now on bulk stuff - mainly helpers
+
+ _trigger: function(type, event, ui) {
+ ui = ui || this._uiHash();
+ $.ui.plugin.call(this, type, [event, ui]);
+ if(type == "drag") this.positionAbs = this._convertPositionTo("absolute"); //The absolute position has to be recalculated after plugins
+ return $.widget.prototype._trigger.call(this, type, event, ui);
+ },
+
+ plugins: {},
+
+ _uiHash: function(event) {
+ return {
+ helper: this.helper,
+ position: this.position,
+ absolutePosition: this.positionAbs, //deprecated
+ offset: this.positionAbs
+ };
+ }
+
+}));
+
+$.extend($.ui.draggable, {
+ version: "1.7",
+ eventPrefix: "drag",
+ defaults: {
+ addClasses: true,
+ appendTo: "parent",
+ axis: false,
+ cancel: ":input,option",
+ connectToSortable: false,
+ containment: false,
+ cursor: "auto",
+ cursorAt: false,
+ delay: 0,
+ distance: 1,
+ grid: false,
+ handle: false,
+ helper: "original",
+ iframeFix: false,
+ opacity: false,
+ refreshPositions: false,
+ revert: false,
+ revertDuration: 500,
+ scope: "default",
+ scroll: true,
+ scrollSensitivity: 20,
+ scrollSpeed: 20,
+ snap: false,
+ snapMode: "both",
+ snapTolerance: 20,
+ stack: false,
+ zIndex: false
+ }
+});
+
+$.ui.plugin.add("draggable", "connectToSortable", {
+ start: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options,
+ uiSortable = $.extend({}, ui, { item: inst.element });
+ inst.sortables = [];
+ $(o.connectToSortable).each(function() {
+ var sortable = $.data(this, 'sortable');
+ if (sortable && !sortable.options.disabled) {
+ inst.sortables.push({
+ instance: sortable,
+ shouldRevert: sortable.options.revert
+ });
+ sortable._refreshItems(); //Do a one-time refresh at start to refresh the containerCache
+ sortable._trigger("activate", event, uiSortable);
+ }
+ });
+
+ },
+ stop: function(event, ui) {
+
+ //If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
+ var inst = $(this).data("draggable"),
+ uiSortable = $.extend({}, ui, { item: inst.element });
+
+ $.each(inst.sortables, function() {
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+
+ inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
+ this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
+
+ //The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: 'valid/invalid'
+ if(this.shouldRevert) this.instance.options.revert = true;
+
+ //Trigger the stop of the sortable
+ this.instance._mouseStop(event);
+
+ this.instance.options.helper = this.instance.options._helper;
+
+ //If the helper has been the original item, restore properties in the sortable
+ if(inst.options.helper == 'original')
+ this.instance.currentItem.css({ top: 'auto', left: 'auto' });
+
+ } else {
+ this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
+ this.instance._trigger("deactivate", event, uiSortable);
+ }
+
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), self = this;
+
+ var checkPos = function(o) {
+ var dyClick = this.offset.click.top, dxClick = this.offset.click.left;
+ var helperTop = this.positionAbs.top, helperLeft = this.positionAbs.left;
+ var itemHeight = o.height, itemWidth = o.width;
+ var itemTop = o.top, itemLeft = o.left;
+
+ return $.ui.isOver(helperTop + dyClick, helperLeft + dxClick, itemTop, itemLeft, itemHeight, itemWidth);
+ };
+
+ $.each(inst.sortables, function(i) {
+
+ //Copy over some variables to allow calling the sortable's native _intersectsWith
+ this.instance.positionAbs = inst.positionAbs;
+ this.instance.helperProportions = inst.helperProportions;
+ this.instance.offset.click = inst.offset.click;
+
+ if(this.instance._intersectsWith(this.instance.containerCache)) {
+
+ //If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
+ if(!this.instance.isOver) {
+
+ this.instance.isOver = 1;
+ //Now we fake the start of dragging for the sortable instance,
+ //by cloning the list group item, appending it to the sortable and using it as inst.currentItem
+ //We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
+ this.instance.currentItem = $(self).clone().appendTo(this.instance.element).data("sortable-item", true);
+ this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
+ this.instance.options.helper = function() { return ui.helper[0]; };
+
+ event.target = this.instance.currentItem[0];
+ this.instance._mouseCapture(event, true);
+ this.instance._mouseStart(event, true, true);
+
+ //Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
+ this.instance.offset.click.top = inst.offset.click.top;
+ this.instance.offset.click.left = inst.offset.click.left;
+ this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
+ this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
+
+ inst._trigger("toSortable", event);
+ inst.dropped = this.instance.element; //draggable revert needs that
+ //hack so receive/update callbacks work (mostly)
+ inst.currentItem = inst.element;
+ this.instance.fromOutside = inst;
+
+ }
+
+ //Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
+ if(this.instance.currentItem) this.instance._mouseDrag(event);
+
+ } else {
+
+ //If it doesn't intersect with the sortable, and it intersected before,
+ //we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
+ if(this.instance.isOver) {
+
+ this.instance.isOver = 0;
+ this.instance.cancelHelperRemoval = true;
+
+ //Prevent reverting on this forced stop
+ this.instance.options.revert = false;
+
+ // The out event needs to be triggered independently
+ this.instance._trigger('out', event, this.instance._uiHash(this.instance));
+
+ this.instance._mouseStop(event, true);
+ this.instance.options.helper = this.instance.options._helper;
+
+ //Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
+ this.instance.currentItem.remove();
+ if(this.instance.placeholder) this.instance.placeholder.remove();
+
+ inst._trigger("fromSortable", event);
+ inst.dropped = false; //draggable revert needs that
+ }
+
+ };
+
+ });
+
+ }
+});
+
+$.ui.plugin.add("draggable", "cursor", {
+ start: function(event, ui) {
+ var t = $('body'), o = $(this).data('draggable').options;
+ if (t.css("cursor")) o._cursor = t.css("cursor");
+ t.css("cursor", o.cursor);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if (o._cursor) $('body').css("cursor", o._cursor);
+ }
+});
+
+$.ui.plugin.add("draggable", "iframeFix", {
+ start: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ $(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
+ $('')
+ .css({
+ width: this.offsetWidth+"px", height: this.offsetHeight+"px",
+ position: "absolute", opacity: "0.001", zIndex: 1000
+ })
+ .css($(this).offset())
+ .appendTo("body");
+ });
+ },
+ stop: function(event, ui) {
+ $("div.ui-draggable-iframeFix").each(function() { this.parentNode.removeChild(this); }); //Remove frame helpers
+ }
+});
+
+$.ui.plugin.add("draggable", "opacity", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data('draggable').options;
+ if(t.css("opacity")) o._opacity = t.css("opacity");
+ t.css('opacity', o.opacity);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data('draggable').options;
+ if(o._opacity) $(ui.helper).css('opacity', o._opacity);
+ }
+});
+
+$.ui.plugin.add("draggable", "scroll", {
+ start: function(event, ui) {
+ var i = $(this).data("draggable");
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') i.overflowOffset = i.scrollParent.offset();
+ },
+ drag: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options, scrolled = false;
+
+ if(i.scrollParent[0] != document && i.scrollParent[0].tagName != 'HTML') {
+
+ if(!o.axis || o.axis != 'x') {
+ if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
+ else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity)
+ i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
+ else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity)
+ i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
+ }
+
+ } else {
+
+ if(!o.axis || o.axis != 'x') {
+ if(event.pageY - $(document).scrollTop() < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
+ else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity)
+ scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
+ }
+
+ if(!o.axis || o.axis != 'y') {
+ if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
+ else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity)
+ scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
+ }
+
+ }
+
+ if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour)
+ $.ui.ddmanager.prepareOffsets(i, event);
+
+ }
+});
+
+$.ui.plugin.add("draggable", "snap", {
+ start: function(event, ui) {
+
+ var i = $(this).data("draggable"), o = i.options;
+ i.snapElements = [];
+
+ $(o.snap.constructor != String ? ( o.snap.items || ':data(draggable)' ) : o.snap).each(function() {
+ var $t = $(this); var $o = $t.offset();
+ if(this != i.element[0]) i.snapElements.push({
+ item: this,
+ width: $t.outerWidth(), height: $t.outerHeight(),
+ top: $o.top, left: $o.left
+ });
+ });
+
+ },
+ drag: function(event, ui) {
+
+ var inst = $(this).data("draggable"), o = inst.options;
+ var d = o.snapTolerance;
+
+ var x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
+ y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
+
+ for (var i = inst.snapElements.length - 1; i >= 0; i--){
+
+ var l = inst.snapElements[i].left, r = l + inst.snapElements[i].width,
+ t = inst.snapElements[i].top, b = t + inst.snapElements[i].height;
+
+ //Yes, I know, this is insane ;)
+ if(!((l-d < x1 && x1 < r+d && t-d < y1 && y1 < b+d) || (l-d < x1 && x1 < r+d && t-d < y2 && y2 < b+d) || (l-d < x2 && x2 < r+d && t-d < y1 && y1 < b+d) || (l-d < x2 && x2 < r+d && t-d < y2 && y2 < b+d))) {
+ if(inst.snapElements[i].snapping) (inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = false;
+ continue;
+ }
+
+ if(o.snapMode != 'inner') {
+ var ts = Math.abs(t - y2) <= d;
+ var bs = Math.abs(b - y1) <= d;
+ var ls = Math.abs(l - x2) <= d;
+ var rs = Math.abs(r - x1) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
+ }
+
+ var first = (ts || bs || ls || rs);
+
+ if(o.snapMode != 'outer') {
+ var ts = Math.abs(t - y1) <= d;
+ var bs = Math.abs(b - y2) <= d;
+ var ls = Math.abs(l - x1) <= d;
+ var rs = Math.abs(r - x2) <= d;
+ if(ts) ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
+ if(bs) ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
+ if(ls) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
+ if(rs) ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
+ }
+
+ if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first))
+ (inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
+ inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
+
+ };
+
+ }
+});
+
+$.ui.plugin.add("draggable", "stack", {
+ start: function(event, ui) {
+
+ var o = $(this).data("draggable").options;
+
+ var group = $.makeArray($(o.stack.group)).sort(function(a,b) {
+ return (parseInt($(a).css("zIndex"),10) || o.stack.min) - (parseInt($(b).css("zIndex"),10) || o.stack.min);
+ });
+
+ $(group).each(function(i) {
+ this.style.zIndex = o.stack.min + i;
+ });
+
+ this[0].style.zIndex = o.stack.min + group.length;
+
+ }
+});
+
+$.ui.plugin.add("draggable", "zIndex", {
+ start: function(event, ui) {
+ var t = $(ui.helper), o = $(this).data("draggable").options;
+ if(t.css("zIndex")) o._zIndex = t.css("zIndex");
+ t.css('zIndex', o.zIndex);
+ },
+ stop: function(event, ui) {
+ var o = $(this).data("draggable").options;
+ if(o._zIndex) $(ui.helper).css('zIndex', o._zIndex);
+ }
+});
+
+})(jQuery);
Property changes on: messageforums-app/src/webapp/js/ui.draggable.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/Scroller.js
===================================================================
--- messageforums-app/src/webapp/js/Scroller.js (revision 0)
+++ 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_0*/
+
+fluid_1_0 = fluid_1_0 || {};
+
+(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_0);
Property changes on: messageforums-app/src/webapp/js/Scroller.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/forum.js
===================================================================
--- messageforums-app/src/webapp/js/forum.js (revision 121251)
+++ messageforums-app/src/webapp/js/forum.js (working copy)
@@ -622,6 +622,22 @@
});
+function resizeFrameForDialog()
+{
+ if (top.location != self.location) {
+ var frame = parent.document.getElementById(window.name);
+ }
+ if( frame ) {
+ var clientH = document.body.clientHeight + 400;
+ $( frame ).height( clientH );
+ }
+ else {
+ throw( "resizeFrame did not get the frame (using name=" + window.name + ")" );
+ }
+}
+
+
+
$(document).ready(function(){
$('.blockMe').click(function(e){
var buttonContainer = $(this).parents('.act');
Index: messageforums-app/src/webapp/js/jquery.bgiframe.js
===================================================================
--- messageforums-app/src/webapp/js/jquery.bgiframe.js (revision 0)
+++ 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
Paragraph
+ *
+ * @param Map settings Optional settings to configure the iframe.
+ * @option String|Number top The iframe must be offset to the top
+ * by the width of the top border. This should be a negative
+ * number representing the border-top-width. If a number is
+ * is used here, pixels will be assumed. Otherwise, be sure
+ * to specify a unit. An expression could also be used.
+ * By default the value is "auto" which will use an expression
+ * to get the border-top-width if it is in pixels.
+ * @option String|Number left The iframe must be offset to the left
+ * by the width of the left border. This should be a negative
+ * number representing the border-left-width. If a number is
+ * is used here, pixels will be assumed. Otherwise, be sure
+ * to specify a unit. An expression could also be used.
+ * By default the value is "auto" which will use an expression
+ * to get the border-left-width if it is in pixels.
+ * @option String|Number width This is the width of the iframe. If
+ * a number is used here, pixels will be assume. Otherwise, be sure
+ * to specify a unit. An experssion could also be used.
+ * By default the value is "auto" which will use an experssion
+ * to get the offsetWidth.
+ * @option String|Number height This is the height of the iframe. If
+ * a number is used here, pixels will be assume. Otherwise, be sure
+ * to specify a unit. An experssion could also be used.
+ * By default the value is "auto" which will use an experssion
+ * to get the offsetHeight.
+ * @option Boolean opacity This is a boolean representing whether or not
+ * to use opacity. If set to true, the opacity of 0 is applied. If
+ * set to false, the opacity filter is not applied. Default: true.
+ * @option String src This setting is provided so that one could change
+ * the src of the iframe to whatever they need.
+ * Default: "javascript:false;"
+ *
+ * @name bgiframe
+ * @type jQuery
+ * @cat Plugins/bgiframe
+ * @author Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
+ */
+$.fn.bgIframe = $.fn.bgiframe = function(s) {
+ // This is only for IE6
+ if ( $.browser.msie && parseInt($.browser.version) <= 6 ) {
+ s = $.extend({
+ top : 'auto', // auto == .currentStyle.borderTopWidth
+ left : 'auto', // auto == .currentStyle.borderLeftWidth
+ width : 'auto', // auto == offsetWidth
+ height : 'auto', // auto == offsetHeight
+ opacity : true,
+ src : 'javascript:false;'
+ }, s || {});
+ var prop = function(n){return n&&n.constructor==Number?n+'px':n;},
+ html = '';
+ return this.each(function() {
+ if ( $('> iframe.bgiframe', this).length == 0 )
+ this.insertBefore( document.createElement(html), this.firstChild );
+ });
+ }
+ return this;
+};
+
+// Add browser.version if it doesn't exist
+if (!$.browser.version)
+ $.browser.version = navigator.userAgent.toLowerCase().match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/)[1];
+
+})(jQuery);
\ No newline at end of file
Property changes on: messageforums-app/src/webapp/js/jquery.bgiframe.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/json2.js
===================================================================
--- messageforums-app/src/webapp/js/json2.js (revision 0)
+++ messageforums-app/src/webapp/js/json2.js (revision 0)
@@ -0,0 +1,475 @@
+/*
+ http://www.JSON.org/json2.js
+ 2009-06-18
+
+ Public Domain.
+
+ NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+ See http://www.JSON.org/js.html
+
+ This file creates a global JSON object containing two methods: stringify
+ and parse.
+
+ JSON.stringify(value, replacer, space)
+ value any JavaScript value, usually an object or array.
+
+ replacer an optional parameter that determines how object
+ values are stringified for objects. It can be a
+ function or an array of strings.
+
+ space an optional parameter that specifies the indentation
+ of nested structures. If it is omitted, the text will
+ be packed without extra whitespace. If it is a number,
+ it will specify the number of spaces to indent at each
+ level. If it is a string (such as '\t' or ' '),
+ it contains the characters used to indent at each level.
+
+ This method produces a JSON text from a JavaScript value.
+
+ When an object value is found, if the object contains a toJSON
+ method, its toJSON method will be called and the result will be
+ stringified. A toJSON method does not serialize: it returns the
+ value represented by the name/value pair that should be serialized,
+ or undefined if nothing should be serialized. The toJSON method
+ will be passed the key associated with the value, and this will be
+ bound to the object holding the key.
+
+ For example, this would serialize Dates as ISO strings.
+
+ Date.prototype.toJSON = function (key) {
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ return this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z';
+ };
+
+ You can provide an optional replacer method. It will be passed the
+ key and value of each member, with this bound to the containing
+ object. The value that is returned from your method will be
+ serialized. If your method returns undefined, then the member will
+ be excluded from the serialization.
+
+ If the replacer parameter is an array of strings, then it will be
+ used to select the members to be serialized. It filters the results
+ such that only members with keys listed in the replacer array are
+ stringified.
+
+ Values that do not have JSON representations, such as undefined or
+ functions, will not be serialized. Such values in objects will be
+ dropped; in arrays they will be replaced with null. You can use
+ a replacer function to replace those with JSON values.
+ JSON.stringify(undefined) returns undefined.
+
+ The optional space parameter produces a stringification of the
+ value that is filled with line breaks and indentation to make it
+ easier to read.
+
+ If the space parameter is a non-empty string, then that string will
+ be used for indentation. If the space parameter is a number, then
+ the indentation will be that many spaces.
+
+ Example:
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}]);
+ // text is '["e",{"pluribus":"unum"}]'
+
+
+ text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+ // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+ text = JSON.stringify([new Date()], function (key, value) {
+ return this[key] instanceof Date ?
+ 'Date(' + this[key] + ')' : value;
+ });
+ // text is '["Date(---current time---)"]'
+
+
+ JSON.parse(text, reviver)
+ This method parses a JSON text to produce an object or array.
+ It can throw a SyntaxError exception.
+
+ The optional reviver parameter is a function that can filter and
+ transform the results. It receives each of the keys and values,
+ and its return value is used instead of the original value.
+ If it returns what it received, then the structure is not modified.
+ If it returns undefined then the member is deleted.
+
+ Example:
+
+ // Parse the text. Values that look like ISO date strings will
+ // be converted to Date objects.
+
+ myData = JSON.parse(text, function (key, value) {
+ var a;
+ if (typeof value === 'string') {
+ a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+ if (a) {
+ return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+ +a[5], +a[6]));
+ }
+ }
+ return value;
+ });
+
+ myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+ var d;
+ if (typeof value === 'string' &&
+ value.slice(0, 5) === 'Date(' &&
+ value.slice(-1) === ')') {
+ d = new Date(value.slice(5, -1));
+ if (d) {
+ return d;
+ }
+ }
+ return value;
+ });
+
+
+ This is a reference implementation. You are free to copy, modify, or
+ redistribute.
+
+ This code should be minified before deployment.
+ See http://javascript.crockford.com/jsmin.html
+
+ USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+ NOT CONTROL.
+*/
+
+/*jslint evil: true */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+ call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+ getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+ lastIndex, length, parse, prototype, push, replace, slice, stringify,
+ test, toJSON, toString, valueOf
+*/
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+var JSON = JSON || {};
+
+(function () {
+
+ function f(n) {
+ // Format integers to have at least two digits.
+ return n < 10 ? '0' + n : n;
+ }
+
+ if (typeof Date.prototype.toJSON !== 'function') {
+
+ Date.prototype.toJSON = function (key) {
+
+ return this.valueOf() ? this.getUTCFullYear() + '-' +
+ f(this.getUTCMonth() + 1) + '-' +
+ f(this.getUTCDate()) + 'T' +
+ f(this.getUTCHours()) + ':' +
+ f(this.getUTCMinutes()) + ':' +
+ f(this.getUTCSeconds()) + 'Z' : null;
+ };
+
+ String.prototype.toJSON =
+ Number.prototype.toJSON =
+ Boolean.prototype.toJSON = function (key) {
+ return this.valueOf();
+ };
+ }
+
+ var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+ gap,
+ indent,
+ meta = { // table of character substitutions
+ '\b': '\\b',
+ '\t': '\\t',
+ '\n': '\\n',
+ '\f': '\\f',
+ '\r': '\\r',
+ '"' : '\\"',
+ '\\': '\\\\'
+ },
+ rep;
+
+
+ function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+ escapable.lastIndex = 0;
+ return escapable.test(string) ?
+ '"' + string.replace(escapable, function (a) {
+ var c = meta[a];
+ return typeof c === 'string' ? c :
+ '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ }) + '"' :
+ '"' + string + '"';
+ }
+
+
+ function str(key, holder) {
+
+// Produce a string from holder[key].
+
+ var i, // The loop counter.
+ k, // The member key.
+ v, // The member value.
+ length,
+ mind = gap,
+ partial,
+ value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+ if (value && typeof value === 'object' &&
+ typeof value.toJSON === 'function') {
+ value = value.toJSON(key);
+ }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+ if (typeof rep === 'function') {
+ value = rep.call(holder, key, value);
+ }
+
+// What happens next depends on the value's type.
+
+ switch (typeof value) {
+ case 'string':
+ return quote(value);
+
+ case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+ return isFinite(value) ? String(value) : 'null';
+
+ case 'boolean':
+ case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+ return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+ case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+ if (!value) {
+ return 'null';
+ }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+ gap += indent;
+ partial = [];
+
+// Is the value an array?
+
+ if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+ length = value.length;
+ for (i = 0; i < length; i += 1) {
+ partial[i] = str(i, value) || 'null';
+ }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+ v = partial.length === 0 ? '[]' :
+ gap ? '[\n' + gap +
+ partial.join(',\n' + gap) + '\n' +
+ mind + ']' :
+ '[' + partial.join(',') + ']';
+ gap = mind;
+ return v;
+ }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+ if (rep && typeof rep === 'object') {
+ length = rep.length;
+ for (i = 0; i < length; i += 1) {
+ k = rep[i];
+ if (typeof k === 'string') {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = str(k, value);
+ if (v) {
+ partial.push(quote(k) + (gap ? ': ' : ':') + v);
+ }
+ }
+ }
+ }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+ v = partial.length === 0 ? '{}' :
+ gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+ mind + '}' : '{' + partial.join(',') + '}';
+ gap = mind;
+ return v;
+ }
+ }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+ if (typeof JSON.stringify !== 'function') {
+ JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+ var i;
+ gap = '';
+ indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+ if (typeof space === 'number') {
+ for (i = 0; i < space; i += 1) {
+ indent += ' ';
+ }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+ } else if (typeof space === 'string') {
+ indent = space;
+ }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+ rep = replacer;
+ if (replacer && typeof replacer !== 'function' &&
+ (typeof replacer !== 'object' ||
+ typeof replacer.length !== 'number')) {
+ throw new Error('JSON.stringify');
+ }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+ return str('', {'': value});
+ };
+ }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+ if (typeof JSON.parse !== 'function') {
+ JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+ var j;
+
+ function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+ var k, v, value = holder[key];
+ if (value && typeof value === 'object') {
+ for (k in value) {
+ if (Object.hasOwnProperty.call(value, k)) {
+ v = walk(value, k);
+ if (v !== undefined) {
+ value[k] = v;
+ } else {
+ delete value[k];
+ }
+ }
+ }
+ }
+ return reviver.call(holder, key, value);
+ }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+ cx.lastIndex = 0;
+ if (cx.test(text)) {
+ text = text.replace(cx, function (a) {
+ return '\\u' +
+ ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+ });
+ }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+ if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+ j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+ return typeof reviver === 'function' ?
+ walk({'': j}, '') : j;
+ }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+ throw new SyntaxError('JSON.parse');
+ };
+ }
+}());
Property changes on: messageforums-app/src/webapp/js/json2.js
___________________________________________________________________
Added: svn:eol-style
+ native
Index: messageforums-app/src/webapp/js/fluidframework-min.js
===================================================================
--- messageforums-app/src/webapp/js/fluidframework-min.js (revision 0)
+++ messageforums-app/src/webapp/js/fluidframework-min.js (revision 0)
@@ -0,0 +1,2168 @@
+/*
+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
+*/
+
+// Declare dependencies.
+/*global jQuery, YAHOO, opera*/
+
+var fluid_1_0 = fluid_1_0 || {};
+var fluid = fluid || fluid_1_0;
+
+(function ($, fluid) {
+
+ fluid.version = "Infusion 1.0";
+
+ /**
+ * Causes an error message to be logged to the console and a real runtime error to be thrown.
+ *
+ * @param {String|Error} message the error message to log
+ */
+ fluid.fail = function (message) {
+ fluid.setLogging(true);
+ fluid.log(message.message? message.message : message);
+ throw new Error(message);
+ //message.fail(); // Intentionally cause a browser error by invoking a nonexistent function.
+ };
+
+ /**
+ * Wraps an object in a jQuery if it isn't already one. This function is useful since
+ * it ensures to wrap a null or otherwise falsy argument to itself, rather than the
+ * often unhelpful jQuery default of returning the overall document node.
+ *
+ * @param {Object} obj the object to wrap in a jQuery
+ */
+ fluid.wrap = function (obj) {
+ return ((!obj || obj.jquery) ? obj : $(obj));
+ };
+
+ /**
+ * If obj is a jQuery, this function will return the first DOM element within it.
+ *
+ * @param {jQuery} obj the jQuery instance to unwrap into a pure DOM element
+ */
+ fluid.unwrap = function (obj) {
+ return obj && obj.jquery && obj.length === 1 ? obj[0] : obj; // Unwrap the element if it's a jQuery.
+ };
+
+ /**
+ * Searches through the supplied object for the first value which matches the one supplied.
+ * @param obj {Object} the Object to be searched through
+ * @param value {Object} the value to be found. This will be compared against the object's
+ * member using === equality.
+ * @return {String} The first key whose value matches the one supplied, or null if no
+ * such key is found.
+ */
+ fluid.keyForValue = function (obj, value) {
+ for (var key in obj) {
+ if (obj[key] === value) {
+ return key;
+ }
+ }
+ return null;
+ };
+
+ /**
+ * This method is now deprecated and will be removed in a future release of Infusion.
+ * See fluid.keyForValue instead.
+ */
+ fluid.findKeyInObject = fluid.keyForValue;
+
+ /**
+ * Clears an object or array of its contents. For objects, each property is deleted.
+ *
+ * @param {Object|Array} target the target to be cleared
+ */
+ fluid.clear = function (target) {
+ if (target instanceof Array) {
+ target.length = 0;
+ }
+ else {
+ for (var i in target) {
+ delete target[i];
+ }
+ }
+ };
+
+
+ // Framework and instantiation functions.
+
+ /**
+ * Fetches a single container element and returns it as a jQuery.
+ *
+ * @param {String||jQuery||element} an id string, a single-element jQuery, or a DOM element specifying a unique container
+ * @return a single-element jQuery of container
+ */
+ fluid.container = function (containerSpec) {
+ var container = containerSpec;
+ if (typeof containerSpec === "string" ||
+ containerSpec.nodeType && (containerSpec.nodeType === 1 || containerSpec.nodeType === 9)) {
+ container = $(containerSpec);
+ }
+
+ // Throw an exception if we've got more or less than one element.
+ if (!container || !container.jquery || container.length !== 1) {
+ if (typeof(containerSpec) !== "string") {
+ containerSpec = container.selector;
+ }
+ fluid.fail({
+ name: "NotOne",
+ message: "A single container element was not found for selector " + containerSpec
+ });
+ }
+
+ return container;
+ };
+
+ /**
+ * Retreives and stores a component's default settings centrally.
+ * @param {boolean} (options) if true, manipulate a global option (for the head
+ * component) rather than instance options.
+ * @param {String} componentName the name of the component
+ * @param {Object} (optional) an container of key/value pairs to set
+ *
+ */
+ var defaultsStore = {};
+ var globalDefaultsStore = {};
+ fluid.defaults = function () {
+ var offset = 0;
+ var store = defaultsStore;
+ if (typeof arguments[0] === "boolean") {
+ store = globalDefaultsStore;
+ offset = 1;
+ }
+ var componentName = arguments[offset];
+ var defaultsObject = arguments[offset + 1];
+ if (defaultsObject !== undefined) {
+ store[componentName] = defaultsObject;
+ return defaultsObject;
+ }
+
+ return store[componentName];
+ };
+
+ /**
+ * Creates a new DOM Binder instance, used to locate elements in the DOM by name.
+ *
+ * @param {Object} container the root element in which to locate named elements
+ * @param {Object} selectors a collection of named jQuery selectors
+ */
+ fluid.createDomBinder = function (container, selectors) {
+ var cache = {}, that = {};
+
+ function cacheKey(name, thisContainer) {
+ return $.data(fluid.unwrap(thisContainer)) + "-" + name;
+ }
+
+ function record(name, thisContainer, result) {
+ cache[cacheKey(name, thisContainer)] = result;
+ }
+
+ that.locate = function (name, localContainer) {
+ var selector, thisContainer, togo;
+
+ selector = selectors[name];
+ thisContainer = localContainer? localContainer: container;
+ if (!thisContainer) {
+ fluid.fail("DOM binder invoked for selector " + name + " without container");
+ }
+
+ if (!selector) {
+ return thisContainer;
+ }
+
+ if (typeof(selector) === "function") {
+ togo = $(selector.call(null, fluid.unwrap(thisContainer)));
+ } else {
+ togo = $(selector, thisContainer);
+ }
+ if (togo.get(0) === document) {
+ togo = [];
+ //fluid.fail("Selector " + name + " with value " + selectors[name] +
+ // " did not find any elements with container " + fluid.dumpEl(container));
+ }
+ if (!togo.selector) {
+ togo.selector = selector;
+ togo.context = thisContainer;
+ }
+ togo.selectorName = name;
+ record(name, thisContainer, togo);
+ return togo;
+ };
+ that.fastLocate = function (name, localContainer) {
+ var thisContainer = localContainer? localContainer: container;
+ var key = cacheKey(name, thisContainer);
+ var togo = cache[key];
+ return togo? togo : that.locate(name, localContainer);
+ };
+ that.clear = function () {
+ cache = {};
+ };
+ that.refresh = function (names, localContainer) {
+ var thisContainer = localContainer? localContainer: container;
+ if (typeof names === "string") {
+ names = [names];
+ }
+ if (thisContainer.length === undefined) {
+ thisContainer = [thisContainer];
+ }
+ for (var i = 0; i < names.length; ++ i) {
+ for (var j = 0; j < thisContainer.length; ++ j) {
+ that.locate(names[i], thisContainer[j]);
+ }
+ }
+ };
+
+ return that;
+ };
+
+ /**
+ * Attaches the user's listeners to a set of events.
+ *
+ * @param {Object} events a collection of named event firers
+ * @param {Object} listeners optional listeners to add
+ */
+ fluid.mergeListeners = function (events, listeners) {
+ if (listeners) {
+ for (var key in listeners) {
+ var value = listeners[key];
+ var keydot = key.indexOf(".");
+ var namespace;
+ if (keydot !== -1) {
+ namespace = key.substring(keydot + 1);
+ key = key.substring(0, keydot);
+ }
+ if (!events[key]) {
+ events[key] = fluid.event.getEventFirer();
+ }
+ var firer = events[key];
+ if (typeof(value) === "function") {
+ firer.addListener(value, namespace);
+ }
+ else if (value && typeof value.length === "number") {
+ for (var i = 0; i < value.length; ++ i) {
+ firer.addListener(value[i], namespace);
+ }
+ }
+ }
+ }
+ };
+
+ /**
+ * Sets up a component's declared events.
+ * Events are specified in the options object by name. There are three different types of events that can be
+ * specified:
+ * 1. an ordinary multicast event, specified by "null.
+ * 2. a unicast event, which allows only one listener to be registered
+ * 3. a preventable event
+ *
+ * @param {Object} that the component
+ * @param {Object} options the component's options structure, containing the declared event names and types
+ */
+ fluid.instantiateFirers = function (that, options) {
+ that.events = {};
+ if (options.events) {
+ for (var event in options.events) {
+ var eventType = options.events[event];
+ that.events[event] = fluid.event.getEventFirer(eventType === "unicast", eventType === "preventable");
+ }
+ }
+ fluid.mergeListeners(that.events, options.listeners);
+ };
+
+ /**
+ * Merges the component's declared defaults, as obtained from fluid.defaults(),
+ * with the user's specified overrides.
+ *
+ * @param {Object} that the instance to attach the options to
+ * @param {String} componentName the unique "name" of the component, which will be used
+ * to fetch the default options from store. By recommendation, this should be the global
+ * name of the component's creator function.
+ * @param {Object} userOptions the user-specified configuration options for this component
+ */
+ fluid.mergeComponentOptions = function (that, componentName, userOptions) {
+ var defaults = fluid.defaults(componentName);
+ that.options = fluid.merge(defaults? defaults.mergePolicy: null, {}, defaults, userOptions);
+ };
+
+
+ /** Expect that an output from the DOM binder has resulted in a non-empty set of
+ * results. If none are found, this function will fail with a diagnostic message,
+ * with the supplied message prepended.
+ */
+ fluid.expectFilledSelector = function (result, message) {
+ if (result && result.length === 0 && result.jquery) {
+ fluid.fail(message + ": selector \"" + result.selector + "\" with name " + result.selectorName +
+ " returned no results in context " + fluid.dumpEl(result.context));
+ }
+ };
+
+ /**
+ * The central initialiation method called as the first act of every Fluid
+ * component. This function automatically merges user options with defaults,
+ * attaches a DOM Binder to the instance, and configures events.
+ *
+ * @param {String} componentName The unique "name" of the component, which will be used
+ * to fetch the default options from store. By recommendation, this should be the global
+ * name of the component's creator function.
+ * @param {jQueryable} container A specifier for the single root "container node" in the
+ * DOM which will house all the markup for this component.
+ * @param {Object} userOptions The configuration options for this component.
+ */
+ fluid.initView = function (componentName, container, userOptions) {
+ var that = {};
+ fluid.expectFilledSelector(container, "Error instantiating component with name \"" + componentName);
+ fluid.mergeComponentOptions(that, componentName, userOptions);
+
+ if (container) {
+ that.container = fluid.container(container);
+ fluid.initDomBinder(that);
+ }
+ fluid.instantiateFirers(that, that.options);
+
+ return that;
+ };
+
+ /** A special "marker object" which is recognised as one of the arguments to
+ * fluid.initSubcomponents. This object is recognised by reference equality -
+ * where it is found, it is replaced in the actual argument position supplied
+ * to the specific subcomponent instance, with the particular options block
+ * for that instance attached to the overall "that" object.
+ */
+ fluid.COMPONENT_OPTIONS = {};
+
+ /** Another special "marker object" representing that a distinguished
+ * (probably context-dependent) value should be substituted.
+ */
+ fluid.VALUE = {};
+
+ /** Construct a dummy or "placeholder" subcomponent, that optionally provides empty
+ * implementations for a set of methods.
+ */
+ fluid.emptySubcomponent = function (options) {
+ var that = {};
+ options = $.makeArray(options);
+ for (var i = 0; i < options.length; ++ i) {
+ that[options[i]] = function () {};
+ }
+ return that;
+ };
+
+ fluid.initSubcomponent = function (that, className, args) {
+ return fluid.initSubcomponents(that, className, args)[0];
+ };
+
+ /** Initialise all the "subcomponents" which are configured to be attached to
+ * the supplied top-level component, which share a particular "class name".
+ * @param {Component} that The top-level component for which sub-components are
+ * to be instantiated. It contains specifications for these subcomponents in its
+ * options structure.
+ * @param {String} className The "class name" or "category" for the subcomponents to
+ * be instantiated. A class name specifies an overall "function" for a class of
+ * subcomponents and represents a category which accept the same signature of
+ * instantiation arguments.
+ * @param {Array of Object} args The instantiation arguments to be passed to each
+ * constructed subcomponent. These will typically be members derived from the
+ * top-level that or perhaps globally discovered from elsewhere. One
+ * of these arguments may be fluid.COMPONENT_OPTIONS in which case this
+ * placeholder argument will be replaced by instance-specific options configured
+ * into the member of the top-level options structure named for the
+ * className
+ * @return {Array of Object} The instantiated subcomponents, one for each member
+ * of that.options[className].
+ */
+
+ fluid.initSubcomponents = function (that, className, args) {
+ var entry = that.options[className];
+ if (!entry) {
+ return;
+ }
+ var entries = $.makeArray(entry);
+ var optindex = -1;
+ var togo = [];
+ args = $.makeArray(args);
+ for (var i = 0; i < args.length; ++ i) {
+ if (args[i] === fluid.COMPONENT_OPTIONS) {
+ optindex = i;
+ }
+ }
+ for (i = 0; i < entries.length; ++ i) {
+ entry = entries[i];
+ if (optindex !== -1 && entry.options) {
+ args[optindex] = entry.options;
+ }
+ if (typeof(entry) !== "function") {
+ var entryType = typeof(entry) === "string"? entry : entry.type;
+ var globDef = fluid.defaults(true, entryType);
+ fluid.merge("reverse", that.options, globDef);
+ togo[i] = entryType === "fluid.emptySubcomponent"?
+ fluid.emptySubcomponent(entry.options) :
+ fluid.invokeGlobalFunction(entryType, args, {fluid: fluid});
+ }
+ else {
+ togo[i] = entry.apply(null, args);
+ }
+
+ var returnedOptions = togo[i]? togo[i].returnedOptions : null;
+ if (returnedOptions) {
+ fluid.merge(that.options.mergePolicy, that.options, returnedOptions);
+ if (returnedOptions.listeners) {
+ fluid.mergeListeners(that.events, returnedOptions.listeners);
+ }
+ }
+ }
+ return togo;
+ };
+
+ /**
+ * Creates a new DOM Binder instance for the specified component and mixes it in.
+ *
+ * @param {Object} that the component instance to attach the new DOM Binder to
+ */
+ fluid.initDomBinder = function (that) {
+ that.dom = fluid.createDomBinder(that.container, that.options.selectors);
+ that.locate = that.dom.locate;
+ };
+
+
+ /** Returns true if the argument is a primitive type **/
+ fluid.isPrimitive = function (value) {
+ var valueType = typeof(value);
+ return !value || valueType === "string" || valueType === "boolean" || valueType === "number";
+ };
+
+ function mergeImpl(policy, basePath, target, source) {
+ var thisPolicy = policy && typeof(policy) !== "string"? policy[basePath] : policy;
+ if (typeof(thisPolicy) === "function") {
+ thisPolicy.apply(null, target, source);
+ return target;
+ }
+ if (thisPolicy === "replace") {
+ fluid.clear(target);
+ }
+
+ for (var name in source) {
+ var path = (basePath? basePath + ".": "") + name;
+ var thisTarget = target[name];
+ var thisSource = source[name];
+ var primitiveTarget = fluid.isPrimitive(thisTarget);
+
+ if (thisSource !== undefined) {
+ if (thisSource !== null && typeof thisSource === 'object' &&
+ !thisSource.nodeType && !thisSource.jquery && thisSource !== fluid.VALUE) {
+ if (primitiveTarget) {
+ target[name] = thisTarget = thisSource instanceof Array? [] : {};
+ }
+ mergeImpl(policy, path, thisTarget, thisSource);
+ }
+ else {
+ if (thisTarget === null || thisTarget === undefined || thisPolicy !== "reverse") {
+ target[name] = thisSource;
+ }
+ }
+ }
+ }
+ return target;
+ }
+
+ /** Merge a collection of options structures onto a target, following an optional policy.
+ * This function is typically called automatically, as a result of an invocation of
+ * fluid.iniView. The behaviour of this function is explained more fully on
+ * the page http://wiki.fluidproject.org/display/fluid/Options+Merging+for+Fluid+Components .
+ * @param policy {Object/String} A "policy object" specifiying the type of merge to be performed.
+ * If policy is of type {String} it should take on the value "reverse" or "replace" representing
+ * a static policy. If it is an
+ * Object, it should contain a mapping of EL paths onto these String values, representing a
+ * fine-grained policy. If it is an Object, the values may also themselves be EL paths
+ * representing that a default value is to be taken from that path.
+ * @param target {Object} The options structure which is to be modified by receiving the merge results.
+ * @param options1, options2, .... {Object} an arbitrary list of options structure which are to
+ * be merged "on top of" the target. These will not be modified.
+ */
+
+ fluid.merge = function (policy, target) {
+ var path = "";
+
+ for (var i = 2; i < arguments.length; ++i) {
+ var source = arguments[i];
+ if (source !== null && source !== undefined) {
+ mergeImpl(policy, path, target, source);
+ }
+ }
+ if (policy && typeof(policy) !== "string") {
+ for (var key in policy) {
+ var elrh = policy[key];
+ if (typeof(elrh) === 'string' && elrh !== "replace") {
+ var oldValue = fluid.model.getBeanValue(target, key);
+ if (oldValue === null || oldValue === undefined) {
+ var value = fluid.model.getBeanValue(target, elrh);
+ fluid.model.setBeanValue(target, key, value);
+ }
+ }
+ }
+ }
+ return target;
+ };
+
+ /** Performs a deep copy (clone) of its argument **/
+
+ fluid.copy = function (tocopy) {
+ return $.extend(true, {}, tocopy);
+ };
+
+ fluid.invokeGlobalFunction = function (functionPath, args, environment) {
+ var func = fluid.model.getBeanValue(window, functionPath, environment);
+ if (!func) {
+ fluid.fail("Error invoking global function: " + functionPath + " could not be located");
+ } else {
+ return func.apply(null, args);
+ }
+ };
+
+
+ // The Model Events system.
+
+ fluid.event = {};
+
+ var fluid_guid = 1;
+ /** Construct an "event firer" object which can be used to register and deregister
+ * listeners, to which "events" can be fired. These events consist of an arbitrary
+ * function signature. General documentation on the Fluid events system is at
+ * http://wiki.fluidproject.org/display/fluid/The+Fluid+Event+System .
+ * @param {Boolean} unicast If true, this is a "unicast" event which may only accept
+ * a single listener.
+ * @param {Boolean} preventable If true the return value of each handler will
+ * be checked for true in which case further listeners will be shortcircuited, and this
+ * will be the return value of fire()
+ */
+
+ fluid.event.getEventFirer = function (unicast, preventable) {
+ var log = fluid.log;
+ var listeners = {};
+ return {
+ addListener: function (listener, namespace, predicate) {
+ if (!listener) {
+ return;
+ }
+ if (unicast) {
+ namespace = "unicast";
+ }
+ if (!namespace) {
+ if (!listener.$$guid) {
+ listener.$$guid = fluid_guid++;
+ }
+ namespace = listener.$$guid;
+ }
+
+ listeners[namespace] = {listener: listener, predicate: predicate};
+ },
+
+ removeListener: function (listener) {
+ if (typeof(listener) === 'string') {
+ delete listeners[listener];
+ }
+ else if (typeof(listener) === 'object' && listener.$$guid) {
+ delete listeners[listener.$$guid];
+ }
+ },
+
+ fire: function () {
+ for (var i in listeners) {
+ var lisrec = listeners[i];
+ var listener = lisrec.listener;
+ if (lisrec.predicate && !lisrec.predicate(listener, arguments)) {
+ continue;
+ }
+ try {
+ var ret = listener.apply(null, arguments);
+ if (preventable && ret === false) {
+ return false;
+ }
+ }
+ catch (e) {
+ log("FireEvent received exception " + e.message + " e " + e + " firing to listener " + i);
+ throw (e);
+ }
+ }
+ }
+ };
+ };
+
+
+ // Model functions
+
+ fluid.model = {};
+
+ /** Copy a source "model" onto a target **/
+ fluid.model.copyModel = function (target, source) {
+ fluid.clear(target);
+ $.extend(true, target, source);
+ };
+
+ /** Parse an EL expression separated by periods (.) into its component segments.
+ * @param {String} EL The EL expression to be split
+ * @return {Array of String} the component path expressions.
+ * TODO: This needs to be upgraded to handle (the same) escaping rules (as RSF), so that
+ * path segments containing periods and backslashes etc. can be processed.
+ */
+ fluid.model.parseEL = function (EL) {
+ return EL.toString().split('.');
+ };
+
+ fluid.model.composePath = function (prefix, suffix) {
+ return prefix === ""? suffix : prefix + "." + suffix;
+ };
+
+ /** This function implements the RSF "DARApplier" **/
+ fluid.model.setBeanValue = function (root, EL, newValue) {
+ var segs = fluid.model.parseEL(EL);
+ for (var i = 0; i < segs.length - 1; i += 1) {
+ if (!root[segs[i]]) {
+ root[segs[i]] = {};
+ }
+ root = root[segs[i]];
+ }
+ root[segs[segs.length - 1]] = newValue;
+ };
+
+ /** Evaluates an EL expression by fetching a dot-separated list of members
+ * recursively from a provided root.
+ * @param root The root data structure in which the EL expression is to be evaluated
+ * @param {string} EL The EL expression to be evaluated
+ * @param environment An optional "environment" which, if it contains any members
+ * at top level, will take priority over the root data structure.
+ * @return The fetched data value.
+ */
+
+ fluid.model.getBeanValue = function (root, EL, environment) {
+ if (EL === "" || EL === null || EL === undefined) {
+ return root;
+ }
+ var segs = fluid.model.parseEL(EL);
+ for (var i = 0; i < segs.length; ++i) {
+ if (!root) {
+ return root;
+ }
+ var segment = segs[i];
+ if (environment && environment[segment]) {
+ root = environment[segment];
+ environment = null;
+ }
+ else {
+ root = root[segment];
+ }
+ }
+ return root;
+ };
+
+
+ // Logging
+ var logging;
+ /** method to allow user to enable logging (off by default) */
+ fluid.setLogging = function (enabled) {
+ if (typeof enabled === "boolean") {
+ logging = enabled;
+ } else {
+ logging = false;
+ }
+ };
+
+ /** Log a message to a suitable environmental console. If the standard "console"
+ * stream is available, the message will be sent there - otherwise either the
+ * YAHOO logger or the Opera "postError" stream will be used. Logging must first
+ * be enabled with a call fo the fluid.setLogging(true) function.
+ */
+ fluid.log = function (str) {
+ if (logging) {
+ str = new Date().toTimeString() + ": " + str;
+ if (typeof(console) !== "undefined") {
+ if (console.debug) {
+ console.debug(str);
+ } else {
+ console.log(str);
+ }
+ }
+ else if (typeof(YAHOO) !== "undefined") {
+ YAHOO.log(str);
+ }
+ else if (typeof(opera) !== "undefined") {
+ opera.postError(str);
+ }
+ }
+ };
+
+ /**
+ * Dumps a DOM element into a readily recognisable form for debugging - produces a
+ * "semi-selector" summarising its tag name, class and id, whichever are set.
+ *
+ * @param {jQueryable} element The element to be dumped
+ * @return A string representing the element.
+ */
+ fluid.dumpEl = function (element) {
+ var togo;
+
+ if (!element) {
+ return "null";
+ }
+ if (element.nodeType === 3 || element.nodeType === 8) {
+ return "[data: " + element.data + "]";
+ }
+ if (element.nodeType === 9) {
+ return "[document: location " + element.location + "]";
+ }
+ if (typeof element.length === "number") {
+ togo = "[";
+ for (var i = 0; i < element.length; ++ i) {
+ togo += fluid.dumpEl(element[i]);
+ if (i < element.length - 1) {
+ togo += ", ";
+ }
+ }
+ return togo + "]";
+ }
+ element = $(element);
+ togo = element.get(0).tagName;
+ if (element.attr("id")) {
+ togo += "#" + element.attr("id");
+ }
+ if (element.attr("class")) {
+ togo += "." + element.attr("class");
+ }
+ return togo;
+ };
+
+ // DOM Utilities.
+
+ /**
+ * Finds the nearest ancestor of the element that passes the test
+ * @param {Element} element DOM element
+ * @param {Function} test A function which takes an element as a parameter and return true or false for some test
+ */
+ fluid.findAncestor = function (element, test) {
+ element = fluid.unwrap(element);
+ while (element) {
+ if (test(element)) {
+ return element;
+ }
+ element = element.parentNode;
+ }
+ };
+
+ /**
+ * Returns a jQuery object given the id of a DOM node. In the case the element
+ * is not found, will return an empty list.
+ */
+ fluid.jById = function (id, dokkument) {
+ dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
+ var element = fluid.byId(id, dokkument);
+ var togo = element? $(element) : [];
+ togo.selector = "#" + id;
+ togo.context = dokkument;
+ return togo;
+ };
+
+ /**
+ * Returns an DOM element quickly, given an id
+ *
+ * @param {Object} id the id of the DOM node to find
+ * @param {Document} dokkument the document in which it is to be found (if left empty, use the current document)
+ * @return The DOM element with this id, or null, if none exists in the document.
+ */
+ fluid.byId = function (id, dokkument) {
+ dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
+ var el = dokkument.getElementById(id);
+ if (el) {
+ if (el.getAttribute("id") !== id) {
+ fluid.fail("Problem in document structure - picked up element " +
+ fluid.dumpEl(el) +
+ " for id " +
+ id +
+ " without this id - most likely the element has a name which conflicts with this id");
+ }
+ return el;
+ }
+ else {
+ return null;
+ }
+ };
+
+ /**
+ * Returns the id attribute from a jQuery or pure DOM element.
+ *
+ * @param {jQuery||Element} element the element to return the id attribute for
+ */
+ fluid.getId = function (element) {
+ return fluid.unwrap(element).getAttribute("id");
+ };
+
+ /**
+ * Allocate an id to the supplied element if it has none already, by a simple
+ * scheme resulting in ids "fluid-id-nnnn" where nnnn is an increasing integer.
+ */
+
+ fluid.allocateSimpleId = function (element) {
+ element = fluid.unwrap(element);
+ if (!element.id) {
+ element.id = "fluid-id-" + (fluid_guid++);
+ }
+ return element.id;
+ };
+
+
+ // Functional programming utilities.
+
+ /** Return a list of objects, transformed by one or more functions. Similar to
+ * jQuery.map, only will accept an arbitrary list of transformation functions.
+ * @param list {Array} The initial array of objects to be transformed.
+ * @param fn1, fn2, etc. {Function} An arbitrary number of optional further arguments,
+ * all of type Function, accepting the signature (object, index), where object is the
+ * list member to be transformed, and index is its list index. Each function will be
+ * applied in turn to each list member, which will be replaced by the return value
+ * from the function.
+ * @return The finally transformed list, where each member has been replaced by the
+ * original member acted on by the function or functions.
+ */
+ fluid.transform = function (list) {
+ var togo = [];
+ for (var i = 0; i < list.length; ++ i) {
+ var transit = list[i];
+ for (var j = 0; j < arguments.length - 1; ++ j) {
+ transit = arguments[j + 1](transit, i);
+ }
+ togo[togo.length] = transit;
+ }
+ return togo;
+ };
+
+ /** Scan through a list of objects, terminating on and returning the first member which
+ * matches a predicate function.
+ * @param list {Array} The list of objects to be searched.
+ * @param fn {Function} A predicate function, acting on a list member. A predicate which
+ * returns any value which is not null or undefined will terminate
+ * the search. The function accepts (object, index).
+ * @param deflt {Object} A value to be returned in the case no predicate function matches
+ * a list member. The default will be the natural value of undefined
+ * @return The first return value from the predicate function which is not null
+ * or undefined
+ */
+ fluid.find = function (list, fn, deflt) {
+ for (var i = 0; i < list.length; ++ i) {
+ var transit = fn(list[i], i);
+ if (transit !== null && transit !== undefined) {
+ return transit;
+ }
+ }
+ return deflt;
+ };
+
+ /** Scan through a list of objects, "accumulating" a value over them
+ * (may be a straightforward "sum" or some other chained computation).
+ * @param list {Array} The list of objects to be accumulated over.
+ * @param fn {Function} An "accumulation function" accepting the signature (object, total, index) where
+ * object is the list member, total is the "running total" object (which is the return value from the previous function),
+ * and index is the index number.
+ * @param arg {Object} The initial value for the "running total" object.
+ * @return {Object} the final running total object as returned from the final invocation of the function on the last list member.
+ */
+ fluid.accumulate = function (list, fn, arg) {
+ for (var i = 0; i < list.length; ++ i) {
+ arg = fn(list[i], arg, i);
+ }
+ return arg;
+ };
+
+ /** Can through a list of objects, removing those which match a predicate. Similar to
+ * jQuery.grep, only acts on the list in-place by removal, rather than by creating
+ * a new list by inclusion.
+ * @param list {Array} The list of objects to be scanned over.
+ * @param fn {Function} A predicate function determining whether an element should be
+ * removed. This accepts the standard signature (object, index) and returns a "truthy"
+ * result in order to determine that the supplied object should be removed from the list.
+ * @return The list, transformed by the operation of removing the matched elements. The
+ * supplied list is modified by this operation.
+ */
+ fluid.remove_if = function (list, fn) {
+ for (var i = 0; i < list.length; ++ i) {
+ if (fn(list[i], i)) {
+ list.splice(i, 1);
+ --i;
+ }
+ }
+ return list;
+ };
+
+ /**
+ * Expand a message string with respect to a set of arguments, following a basic
+ * subset of the Java MessageFormat rules.
+ * http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
+ *
+ * The message string is expected to contain replacement specifications such
+ * as {0}, {1}, {2}, etc.
+ * @param messageString {String} The message key to be expanded
+ * @param args {String/Array of String} An array of arguments to be substituted into the message.
+ * @return The expanded message string.
+ */
+ fluid.formatMessage = function (messageString, args) {
+ if (!args) {
+ return messageString;
+ }
+ if (typeof(args) === "string") {
+ args = [args];
+ }
+ for (var i = 0; i < args.length; ++ i) {
+ messageString = messageString.replace("{" + i + "}", args[i]);
+ }
+ return messageString;
+ };
+
+ /** Converts a data structure consisting of a mapping of keys to message strings,
+ * into a "messageLocator" function which maps an array of message codes, to be
+ * tried in sequence until a key is found, and an array of substitution arguments,
+ * into a substituted message string.
+ */
+ fluid.messageLocator = function (messageBase) {
+ return function (messagecodes, args) {
+ if (typeof(messagecodes) === "string") {
+ messagecodes = [messagecodes];
+ }
+ for (var i = 0; i < messagecodes.length; ++ i) {
+ var code = messagecodes[i];
+ var message = messageBase[code];
+ if (message === undefined) {
+ continue;
+ }
+ return fluid.formatMessage(message, args);
+ }
+ return "[Message string for key " + messagecodes[0] + " not found]";
+ };
+ };
+
+ // Other useful helpers.
+
+ /**
+ * Simple string template system.
+ * Takes a template string containing tokens in the form of "%value".
+ * Returns a new string with the tokens replaced by the specified values.
+ * Keys and values can be of any data type that can be coerced into a string. Arrays will work here as well.
+ *
+ * @param {String} template a string (can be HTML) that contains tokens embedded into it
+ * @param {object} values a collection of token keys and values
+ */
+ fluid.stringTemplate = function (template, values) {
+ var newString = template;
+ for (var key in values) {
+ if (values.hasOwnProperty(key)) {
+ var searchStr = "%" + key;
+ newString = newString.replace(searchStr, values[key]);
+ }
+ }
+ return newString;
+ };
+
+})(jQuery, fluid_1_0);
+/*
+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
+*/
+
+// Declare dependencies.
+/*global jQuery */
+
+var fluid_1_0 = fluid_1_0 || {};
+
+(function ($, fluid) {
+
+ fluid.dom = fluid.dom || {};
+
+ // Node walker function for iterateDom.
+ var getNextNode = function (iterator) {
+ if (iterator.node.firstChild) {
+ iterator.node = iterator.node.firstChild;
+ iterator.depth += 1;
+ return iterator;
+ }
+ while (iterator.node) {
+ if (iterator.node.nextSibling) {
+ iterator.node = iterator.node.nextSibling;
+ return iterator;
+ }
+ iterator.node = iterator.node.parentNode;
+ iterator.depth -= 1;
+ }
+ return iterator;
+ };
+
+ /**
+ * Walks the DOM, applying the specified acceptor function to each element.
+ * There is a special case for the acceptor, allowing for quick deletion of elements and their children.
+ * Return true from your acceptor function if you want to delete the element in question.
+ *
+ * @param {Element} node the node to start walking from
+ * @param {Function} acceptor the function to invoke with each DOM element
+ */
+ fluid.dom.iterateDom = function (node, acceptor) {
+ var currentNode = {node: node, depth: 0};
+ var prevNode = node;
+ while (currentNode.node !== null && currentNode.depth >= 0 && currentNode.depth < fluid.dom.iterateDom.DOM_BAIL_DEPTH) {
+ var deleted = false;
+ if (currentNode.node.nodeType === 1) {
+ deleted = acceptor(currentNode.node, currentNode.depth);
+ }
+ if (deleted) {
+ currentNode.node.parentNode.removeChild(currentNode.node);
+ currentNode.node = prevNode;
+ }
+ prevNode = currentNode.node;
+ currentNode = getNextNode(currentNode);
+ }
+ };
+
+ // Work around IE circular DOM issue. This is the default max DOM depth on IE.
+ // http://msdn2.microsoft.com/en-us/library/ms761392(VS.85).aspx
+ fluid.dom.iterateDom.DOM_BAIL_DEPTH = 256;
+
+ /**
+ * Returns the absolute position of a supplied DOM node in pixels.
+ * Implementation taken from quirksmode http://www.quirksmode.org/js/findpos.html
+ */
+ fluid.dom.computeAbsolutePosition = function (element) {
+ var curleft = 0, curtop = 0;
+ if (element.offsetParent) {
+ do {
+ curleft += element.offsetLeft;
+ curtop += element.offsetTop;
+ element = element.offsetParent;
+ } while (element);
+ return [curleft, curtop];
+ }
+ };
+
+ /**
+ * Checks if the sepcified container is actually the parent of containee.
+ *
+ * @param {Element} container the potential parent
+ * @param {Element} containee the child in question
+ */
+ fluid.dom.isContainer = function (container, containee) {
+ for (; containee; containee = containee.parentNode) {
+ if (container === containee) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+ /** Mockup of a missing DOM function **/
+ fluid.dom.insertAfter = function (newChild, refChild) {
+ var nextSib = refChild.nextSibling;
+ if (!nextSib) {
+ refChild.parentNode.appendChild(newChild);
+ }
+ else {
+ refChild.parentNode.insertBefore(newChild, nextSib);
+ }
+ };
+
+ // The following two functions taken from http://developer.mozilla.org/En/Whitespace_in_the_DOM
+ /**
+ * Determine whether a node's text content is entirely whitespace.
+ *
+ * @param node A node implementing the |CharacterData| interface (i.e.,
+ * a |Text|, |Comment|, or |CDATASection| node
+ * @return True if all of the text content of |nod| is whitespace,
+ * otherwise false.
+ */
+ fluid.dom.isWhitespaceNode = function (node) {
+ // Use ECMA-262 Edition 3 String and RegExp features
+ return !(/[^\t\n\r ]/.test(node.data));
+ };
+
+ /**
+ * Determine if a node should be ignored by the iterator functions.
+ *
+ * @param nod An object implementing the DOM1 |Node| interface.
+ * @return true if the node is:
+ * 1) A |Text| node that is all whitespace
+ * 2) A |Comment| node
+ * and otherwise false.
+ */
+ fluid.dom.isIgnorableNode = function (node) {
+ return (node.nodeType === 8) || // A comment node
+ ((node.nodeType === 3) && fluid.dom.isWhitespaceNode(node)); // a text node, all ws
+ };
+
+ /** Return the element text from the supplied DOM node as a single String */
+ fluid.dom.getElementText = function(element) {
+ var nodes = element.childNodes;
+ var text = "";
+ for (var i = 0; i < nodes.length; ++ i) {
+ var child = nodes[i];
+ if (child.nodeType == 3) {
+ text = text + child.nodeValue;
+ }
+ }
+ return text;
+ };
+
+ /**
+ * Cleanse the children of a DOM node by removing all
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <%-- //designNote: am assuming that the thinking is that once the user is here
+ there is no longer need for the long description context (or as much), so do not put it in
+ the response by default - same goes for attachment list if any --%>
+
+