List view getListItemXmlAttributes method fails with child publication items
I have created a JS class to populate SG/Folder list view data, when items are modified. (As per Jaime's approach) Everything works great when I operate on items in the publication they're created in.
Ex: I open a component or page and the custom locked by
column immediately updates and shows my user name.
However, when I go to a child publication and repeat that process, I get the window asking if I want to localize or edit the parent item. If I select to edit the parent window, the code doesn't work. I haven't quite figured it out yet with initial debugging. Chrome seems to swallow the error, Firefox gives me a cryptic:
Timestamp: 6/22/2012 3:42:54 PM
Error: uncaught exception: [Exception... "Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsIWebProgress.DOMWindow]" nsresult: "0x80004002 (NS_NOINTERFACE)" location: "JS frame :: chrome://browser/content/tabbrowser.xml :: :: line 545" data: no]
Does anyone have any initial ideas? I'll try to post some code later on...
Code from PageEx.js:
Type.registerNamespace("MyCompany.Tridion.RTFExtensions");
/*
* Constructor
*/
MyCompany.Tridion.RTFExtensions.PageEx = function (id) {
Type.enableInterface(this, "MyCompany.Tridion.RTFExtensions.PageEx");
this.addInterface("Tridion.ContentManager.Page", [id]);
var p = this.properties;
p.versionNumberString = undefined;
p.modifiedBy = undefined;
p.lockedBy = undefined;
p.approvalStatus = undefined;
p.publishDate = undefined;
p.previousVersion = undefined;
p.previousApprovalStatus = undefined;
p.customModifiedDate = undefined;
p.initialModifierUserName = undefined;
};
/*
* sends the list xml string for the item
*/
MyCompany.Tridion.RTFExtensions.PageEx.prototype.getListItemXmlAttributes = function (customAttributes) {
var attribs = {};
$extUtils.getListItemXmlAttributes(customAttributes,this, attribs);
return this.callBase("Tridion.ContentManager.Page", "getListItemXmlAttributes", [attribs]);
};
/*
* This method gets called when an item is opened from list view. node parameter has the information
* displayed in the list view as attributes. We are getting cutom data extender column information
* from this xml node and storing it in this class member for returning it from getListItemXmlAttributes method
*/
MyCompany.Tridion.RTFExtensions.PageEx.prototype.setDataFromList = function (node, parentId, timeStamp) {
$extUtils.setDataFromList(node,parentId,timeStamp,this);
this.callBase("Tridion.ContentManager.Page", "setDataFromList", [node, parentId, timeStamp]);
};
/*
* Gets item icon
*/
MyCompany.Tridion.RTFExtensions.PageEx.prototype.getItemIcon = function () {
var icon = this.callBase(this.defaultBase, "getItemIcon");
return icon;
};
Code from utils.js:
// reloads the list view for the given id (used in list view data refresh when JS cant get the required data without reloading)
MyCompany.Tridion.RTFExtensions.Utilities.reloadListView = function (listTcmId) {
var registry = $models.getListsRegistry();
for(var key in registry)
{
var entry = $models.getItem(registry[key]);
if (entry && entry.getParentId() == listTcmId)
{
entry.unload();
return true;
}
}
return false;
}
/*
* This method gets called when an item is opened from list view. node parameter has the information
* displayed in the list view as attributes. We are getting cutom data extender column information
* from this xml node and storing it in this class member for returning it from getListItemXmlAttributes method
*/
MyCompany.Tridion.RTFExtensions.Utilities.setDataFromList = function (node, parentId, timeStamp, itemClicked) {
var p = itemClicked.properties;
if (!timeStamp || timeStamp > itemClicked.getTimeStamp()) {
var tmp;
if (tmp = node.getAttribute('Version')) {
p.versionNumberString = tmp;
p.previousVersion = tmp;
}
if (tmp = node.getAttribute('ModifiedBy')) {
p.modifiedBy = tmp;
p.initialModifierUserName = tmp;
}
if (tmp = node.getAttribute('LockedBy')) {
p.lockedBy = tmp;
}
if (tmp = node.getAttribute('ApprovalStatus')) {
p.approvalStatus = tmp;
p.previousApprovalStatus = tmp;
}
if (tmp = node.getAttribute('PublishDate')) {
p.publishDate = tmp;
}
if (p.customModifiedDate === undefined) {
if (tmp = node.getAttribute('Modified')) {
p.customModifiedDate = tmp;
}
}
}
}
/*
* sends the list xml string for the item in the list view.
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListItemXmlAttributes = function (customAttributes, listViewObject,attribs) {
var p = listViewObject.properties;
$extUtils.getListViewItemLockedByName(p,listViewObject);
if (customAttributes) {
for (var attr in customAttributes) {
attribs[attr] = customAttributes[attr];
}
}
attribs["Version"] = $extUtils.getListViewItemUpdatedVersion(p,listViewObject);
//modified name has to come after the version update...
$extUtils.getListViewItemModifiedByName(p,listViewObject);
attribs["ApprovalStatus"] = $extUtils.getListViewItemApprovalStatus(p,listViewObject);
attribs["PublishDate"] = $extUtils.getListViewItemPublishDate(p,listViewObject);
//set default values
if (p.versionNumberString != undefined) {
var iResult = p.versionNumberString.localeCompare(p.previousVersion);
if (p.previousVersion === undefined || iResult > 0) {
//it's been updated!
p.previousVersion = p.versionNumberString;
p.previousApprovalStatus = p.approvalStatus;
//also need to update modified date
p.customModifiedDate = $extUtils.getListViewItemUpdatedModifiedDate(p,listViewObject);
p.initialModifierUserName = p.modifiedBy;
}
}
attribs["Modified"] = p.customModifiedDate;
attribs["LockedBy"] = p.lockedBy;
attribs["ModifiedBy"] = p.modifiedBy;
};
/*
* This method sets the property of the Revisor owner on the item in the list view. however, if it's not the current user
* we have no way to look that up in JS so we have to reload the list view.
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemModifiedByName = function (p,listViewObject) {
var p = listViewObject.properties;
var xmlDoc = listViewObject.getXmlDocument();
if (xmlDoc) {
//modifier should always exist...
var modifierId = $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:VersionInfo/tcm:Revisor/@xlink:title");
if (modifierId != undefined) {
var u = Tridion.UI.UserSettings.getJsonUserSettings(true);
if (modifierId == u.User.Data.Name) {
var strDescription = u.User.Data.Description.split('(');
p.modifiedBy = strDescription[0];
return;
} else {
//we're in trouble...
//let's hope it's the initial modifier we had...
if (p.previousVersion == p.versionNumberString) {
//whew...
p.modifiedBy = p.initialModifierUserName;
return;
}
if (!$extUtils.reloadListView(listViewObject.getOrganizationalItemId())) {
//hrm. something failed on the reload? not sure what else to do:
p.modifiedBy = modifierId;
}
}
} else {
//shouldn't ever happen.
p.modifiedBy = "";
return;
}
}
};
/*
* This method sets the property of the lock owner on the item in the list view. however, if it's not the current user
* we have no way to look that up in JS so we have to reload the list view.
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemLockedByName = function (p,listViewObject) {
var xmlDoc = listViewObject.getXmlDocument();
if (xmlDoc) {
//this will be user id. no sense getting tcmid... can't look it up without async call
var lockedUserId = $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:VersionInfo/tcm:ItemLock/tcm:User/@xlink:title");
if (lockedUserId != undefined) {
//see if it's the current user. most likely...
var u = Tridion.UI.UserSettings.getJsonUserSettings(true);
if (lockedUserId == u.User.Data.Name) {
var strDescription = u.User.Data.Description.split('(');
p.lockedBy = strDescription[0];
return;
}
//it's not the current user. no synch way to do what we want, plus the JS call doesn't get the workflow version anyway. refresh the parent view
if (!$extUtils.reloadListView(listViewObject.getOrganizationalItemId())) {
//hrm. something failed on the reload? not sure what else to do:
p.lockedBy = lockedUserId;
}
} else {
//clear it out since there's no lock owner
p.lockedBy = "";
}
}
};
/*
* Gets the ApprovalStatus from the item
* This makes absolutely no sense... but for some reason the approval status gets wiped out when this method
* enters. so I had to use a "previous approval status" variable to maintain it. no idea why. I don't see anything
* else that should be touching it... but clearly something clears it out.
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemApprovalStatus = function (p,listViewObject) {
//check if the item has actually been modified.
if (p.versionNumberString != p.previousVersion) {
var xmlDoc = listViewObject.getXmlDocument();
if (xmlDoc) {
p.approvalStatus = $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:Data/tcm:ApprovalStatus/@xlink:title");
}
} else {
p.approvalStatus = p.previousApprovalStatus;
}
if (p.approvalStatus === undefined || p.approvalStatus.toUpperCase() == 'UNAPPROVED') {
var foo = p.approvalStatus;
p.approvalStatus = 'WIP';
}
return p.approvalStatus;
};
/*
* Gets the PublishDate from the item list view
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemPublishDate = function (p,listViewObject) {
//modification won't alter publish date.
var p = listViewObject.properties;
return p.publishDate;
};
/*
* get the modified date for the workflow version, overwrite OOB since that uses last major version
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemUpdatedModifiedDate = function (p,listViewObject) {
var xmlDoc = listViewObject.getXmlDocument();
var modDate = $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:VersionInfo/tcm:RevisionDate");
return modDate;
}
/*
* Gets the updated Version information from the item
*/
MyCompany.Tridion.RTFExtensions.Utilities.getListViewItemUpdatedVersion = function (p,listViewObject) {
var p = listViewObject.properties;
var xmlDoc = listViewObject.getXmlDocument();
var newVersionString = undefined;
if (xmlDoc) {
newVersionString = String.format("{0}.{1}", $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:VersionInfo/tcm:Version"), $xml.getInnerText(xmlDoc, "/tcm:*/tcm:Info/tcm:VersionInfo/tcm:Revision"));
}
if (newVersionString != undefined) {
//want to ensure we're getting a LATER version than we had (because it will try to load the non-workflow version afterwards...
var iResult = newVersionString.localeCompare(p.previousVersion);
if (p.previousVersion === undefined || iResult > 0) {
p.versionNumberString = newVersionString;
} else {
p.versionNumberString = p.previousVersion;
}
} else {
p.versionNumberString = p.previousVersion;
}
return p.versionNumberString;
};
function launchPopup(winURL, winName, winFeatures, winObj) {
// this will hold our opened window
var theWin;
// first check to see if the window already exists
if (winObj != null) {
// the window has already been created, but did the user close it?
// if so, then reopen it. Otherwise make it the active window.
if (!winObj.closed) {
winObj.focus();
return winObj;
}
// otherwise fall through to the code below to re-open the window
}
// if we get here, then the window hasn't been created yet, or it
// was closed by the user.
theWin = window.open(winURL, winName, winFeatures);
return theWin;
}
var $extUtils = MyCompany.Tridion.RTFExtensions.Utilities;
Solution 1:
[Exception... "Component returned failure code: 0x80004002 (NS_NOINTERFACE) [nsIWebProgress.DOMWindow]"
This means that there was no window assigned to the nsIWebProgress object. So it has nowhere to display data.
nsresult: "0x80004002 (NS_NOINTERFACE)" location: "JS frame :: chrome://browser/content/tabbrowser.xml :: :: line 545" data: no]
This is telling you what file is associated with that error. and what line it faulted on.
But the real key is the NS_NOINTERFACE error. Which means the interface has not been registered.
You are using Type.enableInterface()
. Is that a custom method you are declaring somewhere else ? I don't see it. You might want to change that to .registerInterface()
See this link Type Class and Type.registerInterface()