322 lines
15 KiB
JavaScript
322 lines
15 KiB
JavaScript
(function ($) {
|
|
|
|
//jquery plugin for our tree picker
|
|
$.fn.MultiNodeTreePicker = function (ctlId, maxItems, tooltipAjaxUrl, showTooltip, showThumbnail, umbPath, treeType) {
|
|
|
|
/* internal properties */
|
|
var $tree = $(this);
|
|
var ctl = $("#" + ctlId);
|
|
var hiddenField = ctl.find("input[type='hidden']");
|
|
var rightCol = ctl.find(".right");
|
|
var tooltip = null; //the tooltip object (will be defined when it is shown)
|
|
var throbber = null;
|
|
var collapseNode = null; //this is used to track collapsing of tree nodes due to the issue of the nodeClicked event being fired on tree collapse
|
|
|
|
//store a reference to the hidden field in the right columns data collection
|
|
rightCol.data("hiddenField", hiddenField);
|
|
|
|
function getPosition(trigger, tip, conf) {
|
|
// get origin top/left position
|
|
var top = trigger.offset().top,
|
|
left = trigger.offset().left,
|
|
pos = conf.position[0];
|
|
|
|
top -= tip.outerHeight() - conf.offset[0];
|
|
left += trigger.outerWidth() + conf.offset[1];
|
|
|
|
// adjust Y
|
|
var height = tip.outerHeight() + trigger.outerHeight();
|
|
if (pos == 'center') { top += height / 2; }
|
|
if (pos == 'bottom') { top += height; }
|
|
|
|
|
|
// adjust X
|
|
pos = conf.position[1];
|
|
var width = tip.outerWidth() + trigger.outerWidth();
|
|
if (pos == 'center') { left -= width / 2; }
|
|
if (pos == 'left') { left -= width; }
|
|
|
|
return { top: top, left: left };
|
|
}
|
|
|
|
//used to create the tooltip
|
|
var tooltipOptions = {
|
|
tip: "#MNTPTooltip",
|
|
effect: "fade",
|
|
predelay: 0,
|
|
position: 'center left',
|
|
relative: true,
|
|
offset: [30, 0],
|
|
onShow: function () {
|
|
//get the id of the item being queried
|
|
var id = this.getTrigger().next().find("li").attr("rel");
|
|
$.ajax({
|
|
type: "POST",
|
|
data: "{\"id\": " + id + "}",
|
|
dataType: "json",
|
|
url: tooltipAjaxUrl,
|
|
contentType: "application/json; charset=UTF-8",
|
|
success: function (data) {
|
|
var newLocation = (treeType == "content" ? umbPath + "/editContent.aspx?id=" : umbPath + "/editMedia.aspx?id=") + data.d.Id;
|
|
var h = $("<a href='" + newLocation + "'>[edit]</a><h5>ID: " + data.d.Id + "</h5><p><b>Path:</b> " + data.d.Path + "</p><p><i>" + data.d.PathAsNames + "</i></p>");
|
|
h.click(function () {
|
|
|
|
if (!confirm("Are you sure you want to navigate away from this page?\n\nYou may have unsaved changes.\n\nPress OK to continue or Cancel to stay on the current page.")) {
|
|
return false;
|
|
}
|
|
|
|
//this is a VERY dodgy work around for deep linking between sections and pages
|
|
var iframe = UmbClientMgr.mainWindow().jQuery("#deepLinkScriptFrame");
|
|
if (iframe.length == 0) {
|
|
var html = "<html><head><script type='text/javascript'>"
|
|
+ "this.window.top.delayedNavigate = function(url, app) { "
|
|
+ " if (UmbClientMgr.historyManager().getCurrent() == app) {"
|
|
+ " UmbClientMgr.contentFrame(url);"
|
|
+ " }"
|
|
+ " else {"
|
|
+ " var origContentFrameFunc = UmbClientMgr.contentFrame;"
|
|
+ " var newContentFrameFunc = function (location) {"
|
|
+ " UmbClientMgr.contentFrame = origContentFrameFunc;"
|
|
+ " origContentFrameFunc.call(this, url);"
|
|
+ " };"
|
|
+ " UmbClientMgr.contentFrame = newContentFrameFunc;"
|
|
+ " UmbClientMgr.mainTree()._loadedApps['tree_' + app] = null;"
|
|
+ " UmbClientMgr.mainTree().setActiveTreeType(app);"
|
|
+ " UmbClientMgr.mainWindow().location.hash = '#' + app ; "
|
|
+ " }"
|
|
+ "};"
|
|
+ "</script></head><body></body></html>";
|
|
iframe = UmbClientMgr.mainWindow().jQuery("<iframe id='deepLinkScriptFrame'>")
|
|
.append(html)
|
|
.hide()
|
|
.css("width", "0px")
|
|
.css("height", "0px");
|
|
UmbClientMgr.mainWindow().jQuery("body").append(iframe);
|
|
}
|
|
|
|
UmbClientMgr.mainWindow().delayedNavigate(newLocation, treeType);
|
|
|
|
return false;
|
|
});
|
|
throbber.hide().next().html("").append(h).show();
|
|
},
|
|
error: function (data) {
|
|
alert("Error!" + data.d.Message);
|
|
}
|
|
});
|
|
},
|
|
onBeforeShow: function (ev, pos) {
|
|
tooltip = this.getTip();
|
|
//move the tooltip just before the trigger so that it's relatively placed
|
|
this.getTrigger().before(tooltip);
|
|
throbber = tooltip.find(".throbber");
|
|
throbber.show().next().hide();
|
|
},
|
|
events: {
|
|
def: 'click, mouseleave'
|
|
}
|
|
};
|
|
|
|
function StorePickedNodes(hiddenField, rightCol) {
|
|
if (!hiddenField || !rightCol)
|
|
return;
|
|
|
|
var val = "";
|
|
rightCol.find(".item ul.rightNode li").each(function () {
|
|
val += $(this).attr("rel") + ",";
|
|
});
|
|
if (val != "") val = val.substr(0, val.length - 1);
|
|
hiddenField.val(val);
|
|
};
|
|
|
|
function doHighlight(node) {
|
|
var div = node.find('a').find("div:first");
|
|
var origBorder = div.css("border");
|
|
div.css("border", "1px solid #FBC2C4").css("background-color", "#FBE3E4").css("color", "#8a1f11");
|
|
setTimeout(function () { div.attr("style", ""); }, 500);
|
|
}
|
|
|
|
//handler for the node clicking event
|
|
function nodeClickHandler(e, node) {
|
|
//this is a dodgy hack due to a bug in the umb tree that fires the click event
|
|
//twice: http://umbraco.codeplex.com/workitem/29194
|
|
if ($(node).is(":hidden")) {
|
|
collapseNode = node;
|
|
return;
|
|
}
|
|
else {
|
|
//if the collapseNode flag is null, then we're ok
|
|
if (!collapseNode) {
|
|
if (!$(node).hasClass("uc-treenode-noclick")) {
|
|
AddToRight(node);
|
|
}
|
|
else {
|
|
doHighlight($(node));
|
|
}
|
|
}
|
|
else {
|
|
//reset the flag but still do nothing
|
|
collapseNode = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
//handler for when the tree is synced
|
|
function treeSyncEventHandler() {
|
|
//re-add a handler to the tree's nodeClicked event
|
|
$tree.UmbracoTreeAPI().addEventHandler("nodeClicked", nodeClickHandler);
|
|
}
|
|
|
|
//syncs the tree with the item selected on the right
|
|
function SyncItems(node) {
|
|
//first remove the nodeClick event handler as this will fire when the tree is synced
|
|
$tree.UmbracoTreeAPI().removeEventHandler("nodeClicked", nodeClickHandler);
|
|
|
|
//for some reason node syncing doesn't work until a node is selected, so lets just
|
|
//select the root node, then continue to sync the tree.
|
|
$tree.UmbracoTreeAPI().selectNode($tree.find("li:first"));
|
|
|
|
//the path will be available to nodes rendered when the page is rendered so we can do a full sync tree.
|
|
//only the node id will be available for nodes that have been newly selected but this is ok since the
|
|
//nodes are already loaded so the system will be able to sync them.
|
|
var nodeId = node.attr("rel");
|
|
var path = node.attr("umb:nodedata");
|
|
if (!path) path = nodeId;
|
|
|
|
$tree.UmbracoTreeAPI()
|
|
.syncTree(path.toString());
|
|
}
|
|
|
|
//does the adding of a node to the right hand column
|
|
//If "test" is true the node is not added. Instead true is returned if it could have been added
|
|
function AddToRight(node, test) {
|
|
|
|
var $node = $(node);
|
|
|
|
if ($node.hasClass("uc-treenode-noclick")) {
|
|
doHighlight($node);
|
|
return "Item not allowed here";
|
|
}
|
|
|
|
//get the node id of the node selected
|
|
var nodeId = $node.attr("id");
|
|
|
|
//first, check if we've reached the max
|
|
if (maxItems >= 0 && rightCol.find("li").length >= maxItems) {
|
|
doHighlight($node);
|
|
return "No more items can be added";
|
|
}
|
|
|
|
//check if node id already exists in the right panel, also check if it the root node
|
|
//since this should not be selectable
|
|
if (nodeId <= 0 || $tree.find("li:first").attr("id") == nodeId || (rightCol.find("li[rel='" + nodeId + "']").length > 0)) {
|
|
doHighlight($node);
|
|
return nodeId < 0 ? "Item not allowed here" : "Item already added";
|
|
}
|
|
|
|
if (test) {
|
|
return true;
|
|
}
|
|
|
|
//create a copy of the node clicked on the tree
|
|
var jNode = $node.clone().find("a:first")
|
|
//remove un-needed attributes
|
|
jNode.removeAttr("href")
|
|
.removeAttr("umb:nodedata")
|
|
.attr("href", "#")
|
|
.attr("title", "Sync tree");
|
|
|
|
//build a DOM object to put in the right panel
|
|
var inserted = $("<div class='item'><a href='javascript:void(0);' class='info'></a>" +
|
|
"<div class='inner'><ul class='rightNode'>" +
|
|
"<li rel='" + nodeId + "' class='closed'>" +
|
|
"</li></ul><a class='close' title='Remove' href='javascript:void(0);'></a></div></div>")
|
|
.hide()
|
|
.appendTo(rightCol) //add the full div to the right col
|
|
.find(".closed") //get the li element
|
|
.append(jNode) //append the anchor link
|
|
.closest(".item"); //get the item div
|
|
|
|
if (showTooltip) {
|
|
inserted.find(".info").tooltip(tooltipOptions) //add the tooltop
|
|
}
|
|
else {
|
|
//remove the tooltip
|
|
inserted.find(".info").remove();
|
|
}
|
|
|
|
//add the image preview if we need to
|
|
if (showThumbnail) {
|
|
//set the item height to 50px and the width of the inner to 224px
|
|
inserted.css("height", "50px").find(".inner").css("width", "224px");
|
|
var imgViewer = $("<div class='imageViewer'></div>").prependTo(inserted);
|
|
//create the image viewer object, get the API of it and update the image
|
|
imgViewer.UmbracoImageViewer({
|
|
umbPath: umbPath,
|
|
style: "Basic",
|
|
linkTarget: "_blank"
|
|
})
|
|
.UmbracoImageViewerAPI()
|
|
.updateImage(nodeId, function (args) {
|
|
if (imgViewer.find("a").attr("href") == "#") {
|
|
imgViewer.find("img").attr("src", umbPath + "/images/blank.png");
|
|
}
|
|
});
|
|
}
|
|
|
|
inserted.show();
|
|
|
|
//now update the hidden field with the
|
|
//node selection
|
|
StorePickedNodes(hiddenField, rightCol);
|
|
}
|
|
|
|
//remove all trashed nodes
|
|
rightCol.find("li[rel='trashed']").closest(".item").remove();
|
|
|
|
//live click handlers for the removal of items
|
|
$(".item a.close", rightCol).live("click", function () {
|
|
$(this).closest(".item").remove();
|
|
StorePickedNodes(hiddenField, rightCol);
|
|
});
|
|
//create live click handlers to the right hand items
|
|
$(".item ul li a", rightCol).live("click", function () {
|
|
SyncItems($(this).parent());
|
|
});
|
|
|
|
//add a handler to the tree's nodeClicked event
|
|
$tree.UmbracoTreeAPI().addEventHandler("nodeClicked", nodeClickHandler);
|
|
$tree.UmbracoTreeAPI().addEventHandler("syncNotFound", treeSyncEventHandler);
|
|
$tree.UmbracoTreeAPI().addEventHandler("syncFound", treeSyncEventHandler);
|
|
|
|
//create a sortable, drag/drop list and
|
|
//initialize the right panel with previously
|
|
//saved data.
|
|
rightCol.sortable({
|
|
stop: function (event, ui) { StorePickedNodes($(this).data("hiddenField"), $(this)); },
|
|
start: function (event, ui) { if (tooltip) tooltip.hide(); }, //hide the tooltip when sorting
|
|
handle: '.inner'
|
|
});
|
|
|
|
//add the tooltips
|
|
rightCol.find("a.info").tooltip(tooltipOptions);
|
|
|
|
//Register drag and drop action
|
|
//PROOF OF CONCEPT: The user can probably also drop stuff that shouldn't be dropped (e.g., StartNodeID is not considered). The filter function can take care of that
|
|
if (typeof (UmbDragDrop) != "undefined") {
|
|
UmbDragDrop.register(
|
|
//Container to highlight
|
|
rightCol,
|
|
//Drop action
|
|
function (info) {
|
|
AddToRight(info.drag_node);
|
|
},
|
|
//Filter
|
|
function (info) {
|
|
var dropMessage = AddToRight(info.drag_node, true);
|
|
return { canDrop: dropMessage === true, message: dropMessage === true ? "" : dropMessage };
|
|
});
|
|
}
|
|
}
|
|
|
|
})(jQuery); |