blob: dbe4a4dd37215262679016b02a9b2ff1f39fc00d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache 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.apache.org/licenses/LICENSE-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.
*/
var rave = rave || (function () {
var providerMap = {};
var widgetByIdMap = {};
var context = "";
var clientMessages = {};
var openAjaxHub;
/**
* Separate sub-namespace for isolating UI functions and state management
*
* NOTE: The UI implementation has dependencies on jQuery and jQuery UI
*/
var ui = (function () {
var TEXT_FIELD_TEMPLATE = "<tr>{prefLabelTemplate}<td><input type='text' id='{name}' name='{name}' value='{value}' class='{class}'></td></tr>";
var CHECKBOX_TEMPLATE = "<tr>{prefLabelTemplate}<td><input type='checkbox' id='{name}' name='{name}' class='{class}' {checked}></td></tr>";
var SELECT_FIELD_TEMPLATE = "<tr>{prefLabelTemplate}<td><select id='{name}' name='{name}' class='{class}'>{options}</select></td></tr>";
var SELECT_OPTION_TEMPLATE = "<option value='{value}' {selected}>{displayValue}</option>";
var TEXTAREA_TEMPLATE = "<tr>{prefLabelTemplate}<td><textarea id='{name}' name='{name}' rows='5' cols='12' class='{class}'>{value}</textarea></td></tr>";
var HIDDEN_FIELD_TEMPLATE = "<input type='hidden' id='{name}' name='{name}' value='{value}'>";
var PREFS_SAVE_BUTTON_TEMPLATE = "<button type='button' id='{elementId}'>{buttonText}</button>";
var PREFS_CANCEL_BUTTON_TEMPLATE = "<button type='button' id='{elementId}'>{buttonText}</button>";
var NAME_REGEX = /{name}/g;
var VALUE_REGEX = /{value}/g;
var OPTIONS_REGEX = /{options}/g;
var SELECTED_REGEX = /{selected}/g;
var CHECKED_REGEX = /{checked}/g;
var DISPLAY_VALUE_REGEX = /{displayValue}/g;
var PIPE_REGEX = /\|/g;
var ELEMENT_ID_REGEX = /{elementId}/g;
var PREF_LABEL_TEMPLATE_REGEX = /{prefLabelTemplate}/g;
var CLASS_REGEX = /{class}/g;
var BUTTON_TEXT_REGEX = /{buttonText}/g;
var WIDGET_TOGGLE_DISPLAY_COLLAPSED_HTML = '<i class="icon-chevron-down"></i>';
var WIDGET_TOGGLE_DISPLAY_NORMAL_HTML = '<i class="icon-chevron-up"></i>';
var WIDGET_PREFS_LABEL_CLASS = "widget-prefs-label";
var WIDGET_PREFS_LABEL_REQUIRED_CLASS = "widget-prefs-label-required";
var WIDGET_PREFS_INPUT_CLASS = "widget-prefs-input";
var WIDGET_PREFS_INPUT_REQUIRED_CLASS = "widget-prefs-input-required";
var WIDGET_PREFS_INPUT_FAILED_VALIDATION = "widget-prefs-input-failed-validation";
var WIDGET_ICON_BASE_CLASS = "ui-icon";
var WIDGET_BTN_MINIMIZE_CLASS = "ui-icon-arrowthick-1-sw";
var WIDGET_TOGGLE_DISPLAY_COLLAPSED = "ui-icon-triangle-1-e";
var WIDGET_TOGGLE_DISPLAY_NORMAL = "ui-icon-triangle-1-s";
var POPUPS = {
sidebar:{
name:"sidebar",
selector:'.popup.slideout',
markup:'<div class="popup slideout"><div id="slideoutContent"></div></div>',
initialize:function (element) {
element.data('popupType', this.name);
element.show("slide", { direction:"right" }, 'fast');
},
cleanup:function (element) {
element.hide("slide", { direction:"right" }, 'fast', function () {
element.detach();
});
},
singleton:true,
styleMap:{
"box-sizing":"border-box",
position:"fixed",
top:"5%",
right:0,
display:"none",
background:"#FFFFFF",
border:"1px solid #111111",
"border-right-width":"0px",
width:"400px",
"height":"90%",
padding:"10px 10px 20px 30px",
"overflow-y":"auto",
"overflow-x":"visible",
"z-index":5000
}
},
dialog:{
name:"dialog",
selector:'.popup.dialog',
markup:'<div class="popup dialog"></div>',
initialize:function (element) {
element.data('popupType', this.name);
var cfg = {
stack:true,
dialogClass:'raveDialog'
};
element.dialog(cfg);
},
cleanup:function (element) {
element.dialog('destroy');
element.detach();
},
singleton:false,
styleMap:{
}
},
modal_dialog:{
name:"modal_dialog",
selector:'.popup.modal_dialog',
markup:'<div class="popup modal_dialog"></div>',
initialize:function (element) {
element.data('popupType', this.name);
var cfg = {
modal:true,
dialogClass:'raveModal'
};
element.dialog(cfg);
},
cleanup:function (element) {
element.dialog('destroy');
element.detach();
},
singleton:true,
styleMap:{
},
'float':false,
tab:false
}
};
// variable to store whether or not the
// client is a mobile device
var mobileState = false;
function WIDGET_PREFS_EDIT_BUTTON(regionWidgetId) {
return "widget-" + regionWidgetId + "-prefs";
}
function WIDGET_PREFS_SAVE_BUTTON(regionWidgetId) {
return "widget-" + regionWidgetId + "-prefs-save-button";
}
function WIDGET_PREFS_CANCEL_BUTTON(regionWidgetId) {
return "widget-" + regionWidgetId + "-prefs-cancel-button";
}
function WIDGET_PREFS_CONTENT(regionWidgetId) {
return "widget-" + regionWidgetId + "-prefs-content";
}
var uiState = {
widget:null,
currentRegion:null,
targetRegion:null,
targetIndex:null
};
function setMobileState(mobileState) {
this.mobileState = mobileState;
}
function getMobileState() {
return this.mobileState;
}
function init() {
// initialize the sortable regions
getNonLockedRegions().sortable({
connectWith:'.region', // defines which regions are dnd-able
scroll:true, // whether to scroll the window if the user goes outside the areas
opacity:0.5, // the opacity of the object being dragged
revert:true, // smooth snap animation
cursor:'move', // the cursor to show while dnd
handle:'.widget-title-bar', // the draggable handle
forcePlaceholderSize:true, // size the placeholder to the size of the widget
tolerance:'pointer', // change dnd drop zone on mouse-over
start:dragStart, // event listener for drag start
stop:dragStop, // event listener for drag stop
over:dragOver // event listener for drag over
});
initWidgetUI();
}
function dragStart(event, ui) {
adjustRowRegionsHeights();
var $regions = getNonLockedRegions();
// highlight the draggable regions
$regions.addClass("regionDragging");
// remove invisible border so nothing moves
$regions.removeClass("regionNonDragging");
uiState.widget = ui.item.children(".widget").get(0);
uiState.currentRegion = ui.item.parent().get(0);
//for every drag operation, create an overlay for each iframe
//to prevent the iframe from intercepting mouse events
//which kills drag performance
$(".widget").each(function (index, element) {
addOverlay($(element));
});
}
function dragStop(event, ui) {
var $regions = getNonLockedRegions();
// reset padding to 0 after drag on all rows
if ($(".widgetRow").length) {
var rows = $regions.find(".widgetRow");
rows.each(resetRowsRegionsHeight);
}
// remove the draggable regions visible border
$regions.removeClass("regionDragging");
// add an invisible border so nothing moves
$regions.addClass("regionNonDragging");
$(".dnd-overlay").remove();
//Fixes a bug where the jQuery style attribute remains set in chrome
ui.item.attr("style", "");
uiState.targetRegion = ui.item.parent().get(0);
uiState.targetIndex = ui.item.index();
rave.api.rpc.moveWidget(uiState);
clearState();
}
function dragOver(event, ui) {
adjustRowRegionsHeights();
}
// dynamically adjust heights of all regions
function adjustRowRegionsHeights() {
// handle region areas for upper rows
if ($(".upperRow").length) {
var rows = $(".regions").find(".upperRow");
rows.each(adjustUpperRowRegionsHeight);
}
// handle region areas for the bottom row
if ($(".bottomRow").length) {
var row = $(".regions").find(".bottomRow");
adjustBottomRowRegionsHeight(row)
}
}
// adjusts the padding-bottom value of all regions in bottom row to either fill the empty space or
// act as an upper row
function adjustBottomRowRegionsHeight(row) {
resetRowsRegionsHeight(row);
var bodyHeight = $('body').outerHeight();
var windowHeight = $(window).height();
// Instances where no scroll bar currently exists
if (windowHeight >= bodyHeight) {
var pageHeight = $("#pageContent").outerHeight();
var headerHeight = bodyHeight - pageHeight;
var upperRegionsMaxHeights = 0;
if ($(".upperRow").length) {
var rows = $(".regions").find(".upperRow");
for (var x = 0; x < rows.length; x++) {
var rowMaxHeight = getRowRegionsMaxHeight(rows.get(x));
upperRegionsMaxHeights = upperRegionsMaxHeights + rowMaxHeight;
}
}
// determine maximum size possible for bottom region
// 50 px of buffer also removed to prevent scroll-bar from appearing in any cases
var bottomPadding = (windowHeight - 50) - (upperRegionsMaxHeights + headerHeight);
setRowsRegionsHeight(row, bottomPadding);
}
// Instances where scroll bar currently exists, can default to upper row behavior
else {
adjustUpperRowRegionsHeight(row);
}
// refresh sortables cached positions
getNonLockedRegions().sortable("refreshPositions");
}
// adjusts the padding-bottom value of all regions in upper rows to match the value of the
// tallest region in the row
function adjustUpperRowRegionsHeight(row) {
// when called by each, first argument is a number instead of a row value
var row = (typeof row === 'number') ? $(this) : row;
resetRowsRegionsHeight(row);
// sets total region height to the height of tallest region
setRowsRegionsHeight(row, getRowRegionsMaxHeight(row));
// refresh sortables cached positions
getNonLockedRegions().sortable("refreshPositions");
}
// Returns the height of the tallest region in row, minimum 100 px
function getRowRegionsMaxHeight(row) {
var rowChildren = $(row).children();
var maxHeight = 100;
for (var x = 0; x < rowChildren.length; x++) {
if ($(rowChildren.get(x)).outerHeight() > maxHeight) {
maxHeight = $(rowChildren.get(x)).outerHeight();
}
}
return maxHeight;
}
// Restores the padding-bottom value to the original for all regions in given row
function resetRowsRegionsHeight(row) {
// when called by each, first argument is a number instead of a row value
var row = (typeof row === 'number') ? $(this) : row;
var rowChildren = $(row).children();
for (var x = 0; x < rowChildren.length; x++) {
// reset to 5, the initial value before dragging
$(rowChildren.get(x)).css("padding-bottom", 5);
}
}
// Sets the padding-bottom value, so that the total height is the given value for all regions in given row
function setRowsRegionsHeight(row, maxHeight) {
var rowChildren = $(row).children();
for (var x = 0; x < rowChildren.length; x++) {
if ($(rowChildren.get(x)).outerHeight() != maxHeight) {
var defaultPadding = parseInt($(rowChildren.get(x)).css("padding-bottom").replace("px", ""));
$(rowChildren.get(x)).css("padding-bottom", (defaultPadding + maxHeight - $(rowChildren.get(x)).outerHeight()));
}
}
}
function clearState() {
uiState.currentRegion = null;
uiState.targetRegion = null;
uiState.targetIndex = null;
uiState.widget = null;
}
/**
* Takes care of the UI part of the widget rendering. Depends heavily on the HTML structure
*/
function initWidgetUI() {
$(".widget-wrapper").each(function () {
var widgetId = extractObjectIdFromElementId($(this).attr("id"));
styleWidgetButtons(widgetId);
});
}
function initMobileWidgetUI() {
$(".widget-wrapper").each(function () {
var widgetId = extractObjectIdFromElementId($(this).attr("id"));
var widget = rave.getRegionWidgetById(widgetId);
// init the collapse/restore toggle for the title bar
$(this).find(".widget-title-bar-mobile").click({id:widgetId}, toggleCollapseAction);
});
}
function toggleMobileWidget(regionWidgetId) {
var args = {};
args.data = {};
args.data.id = regionWidgetId;
toggleCollapseAction(args);
}
function maximizeAction(args) {
var regionWidgetId = args.data.id;
// display the widget in maximized view
openFullScreenOverlay(regionWidgetId);
var widget = rave.getRegionWidgetById(regionWidgetId);
if (typeof widget != "undefined" && isFunction(widget.maximize)) {
widget.maximize();
}
}
function minimizeAction(args) {
var regionWidgetId = args.data.id;
$(".dnd-overlay").remove();
getNonLockedRegions().sortable("option", "disabled", false);
// display the widget in normal view
$("#widget-" + regionWidgetId + "-wrapper").removeClass("widget-wrapper-canvas").addClass("widget-wrapper");
// hide the widget minimize button
$("#widget-" + regionWidgetId + "-min").hide();
// show the widget menu
$("#widget-" + regionWidgetId + "-widget-menu-wrapper").show();
// show the collapse/restore toggle icon
$("#widget-" + regionWidgetId + "-collapse").show();
var widget = rave.getRegionWidgetById(regionWidgetId);
// if the widget is collapsed execute the collapse function
// otherwise execute the minimize function
if (typeof widget != "undefined") {
if (widget.collapsed && isFunction(widget.collapse)) {
widget.collapse();
} else if (isFunction(widget.minimize)) {
widget.minimize();
}
}
}
function createPopup(popupType) {
var target = POPUPS[popupType.toLowerCase()];
if (!target) {
return rave.log('The popup view requested is not implemented by rave');
}
if (target.singleton && $(target.selector).length>0) {
return $(target.selector).get(0);
}
var popup = $(target.markup);
if (target.styleMap) popup.css(target.styleMap);
$("#pageContent").prepend(popup);
if ($.type(target.initialize) == 'function') target.initialize(popup);
return popup.get(0);
}
function destroyPopup(element) {
element = $(element);
var target = POPUPS[element.data('popupType')];
if (!target) {
rave.log('Rave has detected a destroy request for a popup whose type cannot be detected.');
return element.detach();
}
if ($.type(target.cleanup) == 'function') target.cleanup(element);
}
function editCustomPrefsAction(args) {
var regionWidgetId = args.data.id;
// display the custom edit prefs for the widget in maximized view
openFullScreenOverlay(regionWidgetId);
var widget = rave.getRegionWidgetById(regionWidgetId);
if (typeof widget != "undefined") {
widget.editCustomPrefs();
}
}
function toggleCollapseAction(args) {
var regionWidgetId = args.data.id;
var widget = getRegionWidgetById(regionWidgetId);
// toggle the collapse state of the widget
var newCollapsedValue = !widget.collapsed;
var functionArgs = {"regionWidgetId":regionWidgetId, "collapsed":newCollapsedValue};
// if this type of widget has a collapse or restore callback invoke it upon
// successful persistence
if (typeof widget != "undefined") {
// if this is a collapse action, and the widget has a collapse implementation function,
// attach it as a callback function
if (newCollapsedValue && isFunction(widget.collapse)) {
functionArgs.successCallback = widget.collapse;
}
// if this is a restore action, and the widget has a restore implementation function,
// attach it as a callback function
else if (!newCollapsedValue && isFunction(widget.restore)) {
functionArgs.successCallback = widget.restore;
}
}
// don't persist the collapse / restore state if we are in
// mobile mode because we defaulted all widgets to collapsed
// when initially rendering the mobile view
if (rave.isMobile()) {
rave.doWidgetUiCollapse(functionArgs);
} else {
rave.api.rest.saveWidgetCollapsedState(functionArgs);
}
}
function doWidgetUiCollapse(args) {
// update the in-memory widget with the new collapsed status
rave.getRegionWidgetById(args.regionWidgetId).collapsed = args.collapsed;
// toggle the collapse/restore icon
rave.toggleCollapseWidgetIcon(args.regionWidgetId, args.collapsed);
// if the widget has supplied a collapse or restore
// function, invoke it so each widget provider
// can handle the collapse / restore action independently
if (typeof args.successCallback == 'function') {
args.successCallback();
}
}
/**
* Utility function to generate the html label for a userPref
* based on if it is required or not
*/
function generatePrefLabelMarkup(userPref) {
var markup = [];
var prefLabel = (userPref.required) ? "* " + userPref.displayName : userPref.displayName;
markup.push("<td class='");
markup.push(WIDGET_PREFS_LABEL_CLASS);
if (userPref.required) {
markup.push(" ");
markup.push(WIDGET_PREFS_LABEL_REQUIRED_CLASS);
}
markup.push("'>");
markup.push(prefLabel);
markup.push("</td>");
return markup.join("");
}
/**
* Utility function to generate the css class(es) of a userPref input
* field based on if it is required or not
*/
function generatePrefInputClassMarkup(userPref) {
var markup = [];
markup.push(WIDGET_PREFS_INPUT_CLASS);
if (userPref.required) {
markup.push(" ");
markup.push(WIDGET_PREFS_INPUT_REQUIRED_CLASS);
}
return markup.join("");
}
/**
* Utility function to validate a userPref input element
*/
function validatePrefInput(element) {
var isValid = true;
var jqEl = $(element);
// if the input is required verify it's trimmed input length is > 0
if (jqEl.hasClass(WIDGET_PREFS_INPUT_REQUIRED_CLASS)) {
isValid = $.trim(jqEl.val()).length > 0;
}
return isValid;
}
function editPrefsAction(regionWidgetId) {
var regionWidget = getRegionWidgetById(regionWidgetId);
var userPrefs = regionWidget.metadata.userPrefs;
var hasRequiredUserPrefs = false;
var prefsFormMarkup = [];
prefsFormMarkup.push("<table>");
for (var prefName in userPrefs) {
var userPref = userPrefs[prefName];
var currentPrefValue = regionWidget.userPrefs[userPref.name];
var prefLabelMarkup = generatePrefLabelMarkup(userPref);
var prefInputClassMarkup = generatePrefInputClassMarkup(userPref);
if (userPref.required) {
hasRequiredUserPrefs = true;
}
switch (userPref.dataType) {
case "STRING":
prefsFormMarkup.push(TEXT_FIELD_TEMPLATE.replace(PREF_LABEL_TEMPLATE_REGEX, prefLabelMarkup)
.replace(CLASS_REGEX, prefInputClassMarkup)
.replace(NAME_REGEX, userPref.name)
.replace(VALUE_REGEX, typeof currentPrefValue != "undefined" ? currentPrefValue :
userPref.defaultValue));
break;
case "BOOL":
var checked = typeof currentPrefValue != "undefined" ?
currentPrefValue === 'true' || currentPrefValue === true :
userPref.defaultValue === 'true' || userPref.defaultValue === true;
prefsFormMarkup.push(CHECKBOX_TEMPLATE.replace(PREF_LABEL_TEMPLATE_REGEX, prefLabelMarkup)
.replace(CLASS_REGEX, prefInputClassMarkup)
.replace(NAME_REGEX, userPref.name)
.replace(CHECKED_REGEX, checked ? "checked" : ""));
break;
case "ENUM":
var options = [];
for (var i = 0; i < userPref.orderedEnumValues.length; i++) {
var option = userPref.orderedEnumValues[i];
var selected = currentPrefValue == option.value || (typeof currentPrefValue == "undefined" &&
option.value == userPref.defaultValue);
options.push(SELECT_OPTION_TEMPLATE.replace(VALUE_REGEX, option.value)
.replace(DISPLAY_VALUE_REGEX, option.displayValue)
.replace(SELECTED_REGEX, selected ? "selected" : ""));
}
prefsFormMarkup.push(SELECT_FIELD_TEMPLATE.replace(PREF_LABEL_TEMPLATE_REGEX, prefLabelMarkup)
.replace(CLASS_REGEX, prefInputClassMarkup)
.replace(NAME_REGEX, userPref.name)
.replace(OPTIONS_REGEX, options.join("")));
break;
case "LIST":
var values = typeof currentPrefValue != "undefined" ? currentPrefValue : userPref.defaultValue;
values = values.replace(PIPE_REGEX, "\n");
prefsFormMarkup.push(TEXTAREA_TEMPLATE.replace(PREF_LABEL_TEMPLATE_REGEX, prefLabelMarkup)
.replace(CLASS_REGEX, prefInputClassMarkup)
.replace(NAME_REGEX, userPref.name)
.replace(VALUE_REGEX, values));
break;
default:
prefsFormMarkup.push(HIDDEN_FIELD_TEMPLATE.replace(NAME_REGEX, userPref.name)
.replace(VALUE_REGEX, typeof currentPrefValue != "undefined" ? currentPrefValue :
userPref.defaultValue));
}
}
// if this widget has one or more required inputs display the helper message
if (hasRequiredUserPrefs) {
prefsFormMarkup.push("<tr><td colspan='2' class='widget-prefs-required-text'>" + rave.getClientMessage("widget.prefs.required.title") + "</td></tr>");
}
prefsFormMarkup.push("<tr><td colspan='2'>");
prefsFormMarkup.push(PREFS_SAVE_BUTTON_TEMPLATE.replace(ELEMENT_ID_REGEX, WIDGET_PREFS_SAVE_BUTTON(regionWidget.regionWidgetId))
.replace(BUTTON_TEXT_REGEX, rave.getClientMessage("common.save")));
prefsFormMarkup.push(PREFS_CANCEL_BUTTON_TEMPLATE.replace(ELEMENT_ID_REGEX, WIDGET_PREFS_CANCEL_BUTTON(regionWidget.regionWidgetId))
.replace(BUTTON_TEXT_REGEX, rave.getClientMessage("common.cancel")));
prefsFormMarkup.push("</td></tr>");
prefsFormMarkup.push("</table>");
var prefsElement = $("#" + WIDGET_PREFS_CONTENT(regionWidget.regionWidgetId));
prefsElement.html(prefsFormMarkup.join(""));
$("#" + WIDGET_PREFS_SAVE_BUTTON(regionWidget.regionWidgetId)).click({id:regionWidget.regionWidgetId},
saveEditPrefsAction);
$("#" + WIDGET_PREFS_CANCEL_BUTTON(regionWidget.regionWidgetId)).click({id:regionWidget.regionWidgetId},
cancelEditPrefsAction);
prefsElement.show();
}
function saveEditPrefsAction(args) {
var regionWidget = getRegionWidgetById(args.data.id);
var prefsElement = $("#" + WIDGET_PREFS_CONTENT(regionWidget.regionWidgetId));
var updatedPrefs = {};
var hasValidationErrors = false;
// note that validation of "required" prefs is only done for text and
// textarea types, since those represent STRING and LIST inputs, and
// are the only inputs that could potentially contain empty data
prefsElement.find("*").filter(":input").each(function (index, element) {
switch (element.type) {
case "text":
if (!validatePrefInput(element)) {
hasValidationErrors = true;
$(element).addClass(WIDGET_PREFS_INPUT_FAILED_VALIDATION);
} else {
updatedPrefs[element.name] = $(element).val();
$(element).removeClass(WIDGET_PREFS_INPUT_FAILED_VALIDATION);
}
break;
case "select-one":
case "hidden":
updatedPrefs[element.name] = $(element).val();
break;
case "checkbox":
updatedPrefs[element.name] = $(element).is(':checked').toString();
break;
case "textarea":
if (!validatePrefInput(element)) {
hasValidationErrors = true;
$(element).addClass(WIDGET_PREFS_INPUT_FAILED_VALIDATION);
} else {
var valuesToPersist = [];
var textareaValues = $(element).val().split("\n");
for (var i = 0; i < textareaValues.length; i++) {
var value = $.trim(textareaValues[i]);
if (value.length > 0) {
valuesToPersist.push(value);
}
}
updatedPrefs[element.name] = valuesToPersist.join("|");
$(element).removeClass(WIDGET_PREFS_INPUT_FAILED_VALIDATION);
}
break;
}
});
// check to see if one or more input prefs had validation errors
if (hasValidationErrors) {
// focus on the first input that has validation errors
prefsElement.find("." + WIDGET_PREFS_INPUT_FAILED_VALIDATION).first().focus();
} else {
if (isFunction(regionWidget.savePreferences)) {
regionWidget.savePreferences(updatedPrefs);
}
prefsElement.html("");
prefsElement.hide();
}
}
function cancelEditPrefsAction(args) {
var prefsElement = $("#" + WIDGET_PREFS_CONTENT(args.data.id));
prefsElement.html("");
prefsElement.hide();
}
function addOverlay(jqElm) {
var overlay = $('<div></div>');
var styleMap = {
position:"absolute",
height:jqElm.height(),
width:jqElm.width(),
'z-index':10,
opacity:0.7,
background:"#FFFFFF"
};
// style it and give it the marker class
$(overlay).css(styleMap);
$(overlay).addClass("dnd-overlay");
// add it to the dom before the iframe so it covers it
jqElm.prepend(overlay[0]);
}
function openFullScreenOverlay(regionWidgetId) {
addOverlay($("#pageContent"));
getNonLockedRegions().sortable("option", "disabled", true);
$("#widget-" + regionWidgetId + "-wrapper").removeClass("widget-wrapper").addClass("widget-wrapper-canvas");
// hide the widget menu
$("#widget-" + regionWidgetId + "-widget-menu-wrapper").hide();
// display the widget minimize button
$("#widget-" + regionWidgetId + "-min").show();
// hide the collapse/restore toggle icon in canvas mode
$("#widget-" + regionWidgetId + "-collapse").hide();
}
/**
* Applies styling to the several buttons in the widget toolbar
* @param widgetId identifier of the region widget
*/
function styleWidgetButtons(widgetId) {
var widget = rave.getRegionWidgetById(widgetId);
// init the widget minimize button which is hidden by default
// and only renders when widget is in maximized view
$("#widget-" + widgetId + "-min").click({id: widgetId}, rave.minimizeWidget);
// init the collapse/restore toggle
// conditionally style the icon and setup the event handlers
var $toggleCollapseIcon = $("#widget-" + widgetId + "-collapse");
$toggleCollapseIcon.html((widget.collapsed) ? WIDGET_TOGGLE_DISPLAY_COLLAPSED_HTML : WIDGET_TOGGLE_DISPLAY_NORMAL_HTML);
$toggleCollapseIcon
.click({id:widgetId}, toggleCollapseAction)
.mousedown(function (event) {
// don't allow drag and drop when this item is clicked
event.stopPropagation();
});
$('#widget-' + widgetId + '-toolbar').mousedown(function(event) {
// don't allow drag and drop when this item is clicked
event.stopPropagation();
});
}
/**
* Toggles the display of the widget collapse/restore icon.
* @param widgetId the widgetId of the rendered widget to toggle the icon for
*/
function toggleCollapseWidgetIcon(widgetId, collapsed) {
var $toggleIcon = $("#widget-" + widgetId + "-collapse");
if (collapsed) {
$toggleIcon.html(WIDGET_TOGGLE_DISPLAY_COLLAPSED_HTML);
} else {
$toggleIcon.html(WIDGET_TOGGLE_DISPLAY_NORMAL_HTML);
}
}
/**
* Displays the "empty page" message on the page
*/
function displayEmptyPageMessage() {
$("#emptyPageMessageWrapper").removeClass("hidden");
}
function displayUsersOfWidget(widgetId) {
rave.api.rest.getUsersForWidget({widgetId:widgetId, successCallback:function (data) {
var html = "<ul class='widget-users'>";
for (var i = 0; i < data.length; i++) {
var person = data[i];
var name = (person.displayName) ? person.displayName :
((person.preferredName) ? person.preferredName : person.givenName) + " " + person.familyName;
html += "<li class='widget-user'>" + name + "</li>";
}
html += "</ul>";
$("<div class='dialog widget-users-dialog' title='" + $("#widget-" + widgetId + "-title").text().trim() + " " + rave.getClientMessage("widget.users.added_by") + "'>" + html + "</div>").dialog({
modal:true,
buttons:[
{text:"Close", click:function () {
$(this).dialog("close");
}}
]
});
}});
}
function showInfoMessage(message) {
$("<div />", {'class':'alert alert-success navbar-spacer', 'text':message})
.hide()
.prependTo("body")
.slideDown('fast').delay(5000)
.slideUp(function () {
$(this).remove();
});
}
function getNonLockedRegions() {
return $(".region:not(.region-locked)");
}
return {
init:init,
initMobile:initMobileWidgetUI,
toggleCollapseWidgetIcon:toggleCollapseWidgetIcon,
maximizeAction:maximizeAction,
minimizeAction:minimizeAction,
createPopup:createPopup,
destroyPopup:destroyPopup,
editPrefsAction:editPrefsAction,
editCustomPrefsAction:editCustomPrefsAction,
setMobileState:setMobileState,
getMobileState:getMobileState,
doWidgetUiCollapse:doWidgetUiCollapse,
toggleMobileWidget:toggleMobileWidget,
displayEmptyPageMessage:displayEmptyPageMessage,
displayUsersOfWidget:displayUsersOfWidget,
showInfoMessage:showInfoMessage
};
})();
function getClientMessage(key) {
return clientMessages[key];
}
function addClientMessage(key, message) {
return clientMessages[key] = message;
}
function registerWidget(widgetsByRegionIdMap, regionId, widget) {
if (!widgetsByRegionIdMap.hasOwnProperty(regionId)) {
widgetsByRegionIdMap[regionId] = [];
}
widgetsByRegionIdMap[regionId].push(widget);
}
function initializeProviders() {
//Current providers are rave.wookie and rave.opensocial.
//Providers register themselves when loaded, so
//JavaScript library importing order is important.
//See page.jsp for example.
for (var key in providerMap) {
providerMap[key].init();
}
}
function createNewOpenAjaxHub() {
if(typeof OpenAjax == "undefined"){
throw "No implementation of OpenAjax found. " +
"Please ensure that an implementation has been included in the page.";
}
return new OpenAjax.hub.ManagedHub({
onSubscribe:function (topic, container) {
log(container.getClientID() + " subscribes to this topic '" + topic + "'");
return true;
},
onUnsubscribe:function (topic, container) {
log(container.getClientID() + " unsubscribes from this topic '" + topic + "'");
return true;
},
onPublish:function (topic, data, pcont, scont) {
log(pcont.getClientID() + " publishes '" + data + "' to topic '" + topic + "' subscribed by " + scont.getClientID());
return true;
}
});
}
function getOpenAjaxHubInstance() {
if(typeof openAjaxHub == "undefined" || openAjaxHub == null) {
openAjaxHub = createNewOpenAjaxHub();
}
return openAjaxHub;
}
function resetOpenAjaxHubInstance() {
openAjaxHub = null;
}
function initializeWidgets(widgetsByRegionIdMap) {
//We get the widget objects in a map keyed by region ID. The code below converts that map into a flat array
//of widgets with all the top widgets in each region first, then the seconds widgets in each region, then the
//third, etc until we have all widgets in the array. This allows us to render widgets from left to right and
//top to bottom to give the best user experience possible (rendering the top widgets first).
//Note that this strategy relies on the javascript implementation to enumerate object properties in order:
//http://stackoverflow.com/questions/280713/elements-order-in-a-for-in-loop
//However this should at least get us to render the top "row" first, then the second "row", ... in any browser.
//If this turns out to be a major concern we can change the widgetsByRegionIdMap to a 2D array instead of a map.
var widgets = [];
var regionWidget;
for (var i = 0; ; i++) {
var foundGadgets = false;
for (var regionWidgets in widgetsByRegionIdMap) {
regionWidgets = widgetsByRegionIdMap[regionWidgets];
if (regionWidgets.length > i) {
foundGadgets = true;
regionWidget = regionWidgets[i];
// if client is viewing in mobile mode
// default to collapsed state
if (rave.isMobile()) {
regionWidget.collapsed = true;
}
widgets.push(regionWidget);
}
}
if (!foundGadgets) {
break;
}
}
if (widgets.length == 0) {
rave.displayEmptyPageMessage();
} else {
//Initialize the widgets for supported providers
for (var j = 0; j < widgets.length; j++) {
var widget = widgets[j];
initializeWidget(widget);
widgetByIdMap[widget.regionWidgetId] = widget;
}
}
}
function initializeWidget(widget) {
if (widget.type == "DISABLED") {
renderDisabledWidget(widget.regionWidgetId, unescape(widget.disabledMessage));
return;
}
var provider = providerMap[widget.type];
if (typeof provider == "undefined") {
renderErrorWidget(widget.regionWidgetId, rave.getClientMessage("widget.provider.error"));
} else {
provider.initWidget(widget);
}
}
function addProviderToList(provider) {
if (provider.hasOwnProperty("init")) {
providerMap[provider.TYPE] = provider;
} else {
throw "Attempted to register invalid provider";
}
}
function extractObjectIdFromElementId(elementId) {
var tokens = elementId.split("-");
return tokens.length > 2 && tokens[0] == "widget" || tokens[0] == "region" ? tokens[1] : null;
}
function renderErrorWidget(id, message) {
$("#widget-" + id + "-body").html(message);
}
function renderDisabledWidget(id, message) {
$("#widget-" + id + "-body").html(message);
}
function updateContext(contextPath) {
context = contextPath;
}
function getContext() {
return context;
}
function getRegionWidgetById(regionWidgetId) {
return widgetByIdMap[regionWidgetId];
}
function viewPage(pageId) {
var fragment = (pageId != null) ? ("/" + pageId) : "";
window.location.href = rave.getContext() + "page/view" + fragment;
}
function viewWidgetDetail(widgetId, referringPageId) {
window.location.href = rave.getContext() + "store/widget/" + widgetId + "?referringPageId=" + referringPageId;
}
/**
* Utility function to determine if a javascript object is a function
* @param obj the object to check
* @return true if object is a function, false otherwise
*/
function isFunction(obj) {
return (typeof obj == "function");
}
/**
* Determines if a page is empty (has zero widgets)
*/
function isPageEmpty() {
return $.isEmptyObject(widgetByIdMap);
}
/**
* Removes a regionWidgetId from the internal widget map
* @param regionWidgetId the region widget id to remove
*/
function removeWidgetFromMap(regionWidgetId) {
delete widgetByIdMap[regionWidgetId];
}
/**
* Logs to a console object if it exists
* @param message the message to log
*/
function log(message) {
if (typeof console != "undefined" && console.log) {
console.log(message);
}
}
/**
* Public API
*/
return {
/**
* Registers the specified widget into the widgetsByRegionIdMap under the specified regionId.
* @param widgetsByRegionIdMap The map.
* @param regionId The regionId.
* @param widget The widget.
*/
registerWidget:registerWidget,
/**
* Initialize all of the registered providers
*/
initProviders:initializeProviders,
/**
* Initializes the given set of widgets
* @param widgets a map of widgets by regionId
*
* NOTE: widget object must have at a minimum the following properties:
* type,
* regionWidgetId
*/
initWidgets:initializeWidgets,
/**
* Initialize Rave's drag and drop facilities
*/
initUI:ui.init,
/**
* Initialize the mobile UI
*/
initMobileUI:ui.initMobile,
/**
* Parses the given string conforming to a rave object's DOM element ID and return
* the RegionWidget id
*
* NOTE: assumes convention of widget-RegionWidgetId-body|title|chrome|etc
* Supported rave objects:
* Region
* RegionWidget
*
* @param elementId the ID of the DOM element containing the widget
*/
getObjectIdFromDomId:extractObjectIdFromElementId,
/**
* Registers a new provider with Rave. All providers MUST have init and initWidget functions as well as a
* TYPE property exposed in its public API
*
* @param provider a valid Rave widget provider
*/
registerProvider:addProviderToList,
/**
* Renders an error in place of the widget
*
* @param id the RegionWidgetId of the widget to render in error mode
* @param message The message to display to the user
*/
errorWidget:renderErrorWidget,
/**
* Sets the context path for the Rave web application
*
* @param contextPath the context path of the rave webapp
*/
setContext:updateContext,
/**
* Gets the current context
*/
getContext:getContext,
/**
* Gets a regionwidget by region widget id
*/
getRegionWidgetById:getRegionWidgetById,
/**
* View a page
*
* @param pageId the pageId to view, or if null, the user's default page
*/
viewPage:viewPage,
/**
* View the widget detail page of a widget
*
* @param widgetId to widgetId to view
* @param referringPageId the entityId of the page the call is coming from
*/
viewWidgetDetail:viewWidgetDetail,
/**
* Toggles the collapse/restore icon of the rendered widget
*
* @param widgetId the widgetId of the rendered widget to toggle
*/
toggleCollapseWidgetIcon:ui.toggleCollapseWidgetIcon,
/***
* Utility function to determine if a javascript object is a function or not
*
* @param obj the object to check
* @return true if obj is a function, false otherwise
*/
isFunction:isFunction,
/***
* Maximize the widget view
*
* @param args the argument object
*/
maximizeWidget:ui.maximizeAction,
/***
* Minimize the widget view (render in non full-screen mode)
*
* @param args the argument object
*/
minimizeWidget:ui.minimizeAction,
/***
* Create a new popup in the rave container
*
* @param popupType the type of popup that will be created
* @return the new dom element created
*/
createPopup:ui.createPopup,
/***
* Destroy a popup currently active in the rave container
*
* @param element the popup dom element
*/
destroyPopup:ui.destroyPopup,
/***
* Display the inline edit prefs section for widget preferences inside
* the widget.
*
* @param regionWidgetId the regionWidgetId of the widget
*
*/
editPrefs:ui.editPrefsAction,
/***
* "Preferences" view
*
* @param args the argument object
*/
editCustomPrefs:ui.editCustomPrefsAction,
/***
* Get the mobile state - used by the UI to render mobile or normal content
*
*/
isMobile:ui.getMobileState,
/***
* Set the mobile state - used by the UI to render mobile or normal content
*
* @param mobileState boolean to represent the mobile state
*
*/
setMobile:ui.setMobileState,
/**
* Performs the client side work of collapsing/restoring a widget
* @param args
*/
doWidgetUiCollapse:ui.doWidgetUiCollapse,
/**
* Toggles a mobile widget collapse/restore
* @param args
*/
toggleMobileWidget:ui.toggleMobileWidget,
/**
* Determines if a page is empty (has zero widgets)
* @param widgetByIdMap the map of widgets on the page
*/
isPageEmpty:isPageEmpty,
/**
* Removes a regionWidgetId from the internal widget map
* @param regionWidgetId the region widget id to remove
*/
removeWidgetFromMap:removeWidgetFromMap,
/**
* Displays the "empty page" message on the page
*/
displayEmptyPageMessage:ui.displayEmptyPageMessage,
/**
* Displays the users of a supplied widgetId in a dialog box
*/
displayUsersOfWidget:ui.displayUsersOfWidget,
/**
* Displays an info message at the top of the page.
* @param message The message to display.
*/
showInfoMessage:ui.showInfoMessage,
/**
* Returns a language specific message based on the supplied key
*
* @param key the key of the message
*/
getClientMessage:getClientMessage,
/**
* Adds a message to the internal client message map
*
* @param key
* @param message
*/
addClientMessage:addClientMessage,
/**
* Gets the singleton Managed OpenAJAX 2.0 Hub
*/
getManagedHub: getOpenAjaxHubInstance,
/**
* Resets the managed hub
*/
resetManagedHub: resetOpenAjaxHubInstance,
/**
* Logs a message to a central logging facility (console by default)
*
* @param message the message to log
*/
log:log
}
})();