I managed to get a new ribbon group by following the article mentioned in How to create the custom buttons horizontally one below the other in ribbon of Tridion
I'm now trying to get a Javascript running whenever something changes in the Gui (hiding/showing buttons).
I have this in the configuration:
<!-- In the cfg:groups part -->
<cfg:group name="ClientGuiMods.ContentGroup" description="">
<cfg:fileset>
<cfg:file type="script">/Scripts/CreateRibbonGroup.js</cfg:file>
</cfg:fileset>
<cfg:dependencies>
<cfg:dependency>Tridion.Web.UI.Editors.CME</cfg:dependency>
</cfg:dependencies>
</cfg:group>
<!-- In the ribbontoolbars add part -->
<ext:extension pageid="HomePage" name="Content" assignid="ContentGroupId">
<ext:group>~/Scripts/ContentGroup.ascx</ext:group>
<ext:dependencies>
<cfg:dependency>ClientGuiMods.ContentGroup</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="DashboardView">
<ext:control id="DashboardToolbar" />
</ext:view>
</ext:apply>
</ext:extension>
And this in the Javascript:
Type.registerNamespace("ClientGuiMods");
ClientGuiMods.ContentGroup = function ContentGroup(element)
{
console.log('RibbonGroupCreated');
Tridion.OO.enableInterface(this, "ClientGuiMods.ContentGroup");
this.addInterface("Tridion.Controls.RibbonItemsGroup", [element]);
};
I've tried different arguments for this.addInterface(), but it never gets called. Is this the correct way? Or is there maybe another way to get a script called on the Home ribbon toolbar?
I never really looked at the group as anything other than a container for commands (read buttons). So the only interface I used is Tridion.Cme.Command on the button JavaScript.
But I think what you are looking for is the ControlResource which you can specify in your ContentGroup.ascx.cs
using Tridion.Web.UI.Core;
using Tridion.Web.UI.Controls;
using Tridion.Web.UI.Core.Controls;
namespace ClientGuiMods
{
[ControlResources("ClientGuiMods.ContentGroup")]
public class ContentGroup : TridionUserControl
{
}
}
Now, you can use the Tridion.ControlBase interface in your JavaScript.
Type.registerNamespace("ClientGuiMods");
ClientGuiMods.ContentGroup = function ContentGroup(element) {
console.log('RibbonGroupCreated');
Tridion.OO.enableInterface(this, "ClientGuiMods.ContentGroup");
this.addInterface("Tridion.ControlBase", [element]);
};
ClientGuiMods.ContentGroup.prototype.initialize = function ContentGroup$initialize() {
// the control is initialized here, we can use the following properties now
var props = this.properties;
var controls = props.controls;
var container = this.getElement();
};
#Bart, I tried the solution, but couln't get that to work.
Digging a bit further in the Javascripts in chrome I found there is no hook to fire any extra Javascript as a RibbonGroup (correct me if I'm wrong).
I did however find a way to get to the 'HomePage' RibbonPage and get it to fire events from there.
The extra thing I need is a "c:pagetype='Homepage'" on the RibbonPage named HomePage in the DOM, which isn't there by default. This can be set by including a load event script at the end.
So now my script looks like this.
Type.registerNamespace("ClientGuiMods");
ClientGuiMods.CreateRibbonPage = function CreateRibbonPage(element)
{
Tridion.OO.enableInterface(this, "ClientGuiMods.CreateRibbonPage");
this.addInterface("Tridion.Controls.RibbonPage", [element]);
};
ClientGuiMods.CreateRibbonPage.prototype.updateState = function CreateRibbonPage$updateState(stateObject)
{
//...
//Ribbonpage logic to update the state of your buttons and groups
};
console.log('Homepage: ' + document.getElementById('HomePage')); //.setAttribute('c:pagetype', 'HomePage');
var ClientScripts = {
registerHomepage: function() {
console.log('adding c:pagetype att');
var homepage = document.getElementById('HomePage');
if (homepage) {
homepage.setAttribute('c:pagetype', 'HomePage');
}
}
}
if (document.addEventListener && !Tridion.Utils.Dom.isIE)
$evt.addEventHandler(window, "DOMContentLoaded", ClientScripts.registerHomepage);
else
$evt.addEventHandler(window, "readystatechange", ClientScripts.registerHomepage);
Tridion.Controls.Deck.registerPageType(ClientGuiMods.CreateRibbonPage, "HomePage");
I might be off mark here, but it sounds like you have a series of buttons under the same group, and you want to have them behave consistently in terms of availability.
I had a similar case where I need to fire the same event on Save, Save & Close and Save & New operations. What I ended up doing was to write the code as a Save command extension (broadly based off Jaime's details here) and then, from the SaveClose and SaveNew extensions I would call the Save._isEnabled and Save._isAvailable functions to determine whether my commands were available, and the Save._execute whenever the editors clicks on SaveClose & SaveNew.
Not as elegant as Peter's suggestion though, but got the job done.
Related
How to create button in context menu on component/page right click.
I tried to configure my extension.config file by refering as mentioned in "http://www.julianwraith.com/2010/10/helloworld-extension-for-tridion-2011/" but i was unable to see any button available.
Could anyone suggest me.
Check your configuration file (F:\Program Files (x86)\Tridion\customizations\HelloWorld\config\HelloWorld.config) for unclosed tags or some typo.
To create button in context menu:
Check that your HelloWorld.config extends contextmenu node
<ext:contextmenus>
<ext:add>
<ext:extension name="Hello World" assignid="" insertbefore="cm_preview">
<ext:menudeclaration>
<cmenu:ContextMenuItem id="HelloWorld" name="Hello World" command="HelloWorld"/>
</ext:menudeclaration>
<ext:dependencies>
<cfg:dependency>RandomStringThatNeedsToBeCompiled2</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="DashboardView"/>
</ext:apply>
</ext:extension>
</ext:add>
</ext:contextmenus>
Modify your helloworld.js to enable button only for Components
Common.Tridion.MVP.HelloWorld.prototype.isAvailable = function HelloWorld$isAvailable(selection)
{
var itemID = selection.getItem(0);
if ($models.getItemType(itemID) != $const.ItemType.COMPONENT) {
return false;
}
};
Don't forget to clear your cache
I need to create buttons one below the other in tridion ribbon.
I have created an usercontrol and it was appearing on the ribbon but in disabled mode.
In the "http://tridiondeveloper.com/ribbon-item-group"; it was mentioned to include <ext:issmallbutton>true</ext:issmallbutton> inside my extension element in the configuration. I have included it in the extension.config file. But i am facing error like "Loading extension failed - has invalid child element 'issmallbutton'. So, currently i ignored this step and the buttons were in disabled mode.
Could you please let me understand where i need to add this.(<ext:issmallbutton>true</ext:issmallbutton> ) and to make the buttons enable.
As indicated by Jeremy's answer, you don't need the ext:issmallbutton to enable your button (you mention my article on Tridion Developer, where I specifically state that the ext:issmallbutton is not to be used when you want to stack buttons on top of eachother).
You probably should try to debug your JavaScript and see what is happening in your _isAvailable(selection, pipeline) and _isEnabled(selection, pipeline) methods.
The isAvailable method should indicate whether the command is applicable for the selected item(s) and the isEnabled method indicates whether the command can be executed. I usually just let the isEnabled method return the result of the isAvailable one (since when the button is available, it should most of the time also be enabled). An example of how to enable a button when you have selected a Page would look something like this:
Example.PageBtn.prototype._isAvailable = function PageBtn$_isAvailable(selection, pipeline) {
if (pipeline) {
pipeline.stop = false;
}
if (selection.getCount() == 1) {
var itemType = $models.getItemType(selection.getItem(0));
return itemType && (itemType == $const.ItemType.PAGE);
}
return false;
};
Example.PageBtn.prototype._isEnabled = function PageBtn$_isEnabled(selection, pipeline) {
if (pipeline) {
pipeline.stop = false;
}
return this._isAvailable(selection);
};
Now the ext:issmallbutton element has nothing to do with this all, but if you would like to know where that should be used exactly, it is supposed to go inside the ext:extensionelement like so:
<ext:extension assignid="PageBtn" groupid="MyGroup" name="Example" pageid="HomePage">
<ext:command>PageBtn</ext:command>
<ext:title>Example</ext:title>
<ext:issmallbutton>true</ext:issmallbutton>
<ext:dependencies>
<cfg:dependency>Example.Commands</cfg:dependency>
</ext:dependencies>
<ext:apply>
<ext:view name="DashboardView">
<ext:control id="DashboardToolbar" />
</ext:view>
</ext:apply>
</ext:extension>
You can find more information in Setting up a SDL Tridion 2011 GUI extension in 8 steps.
To enable a button you need its isEnabled method to return true. issmallbutton only determines the size of the button in the toolbar. For information on how to create a button extension please look at the many other questions on this same subject...
Regarding the AsyncFileUpload control in .net, the control will execute the file upload once i select a file. In my concern is, is it possible to disable the upload once i select a file so that i could process the upload asynchronously with a submit button.
I know this is old, but I beat my head on this for a while, so for whoever might be interested.
My first thought was to disable the file input underneath the control.
I was able to disable the control but unfortunately, it stopped working. When the server fired AsyncFileUpload_UploadComplete the input was diabled so there wasn't a file to read.
<script>
function disableFileUpload(on) {
if (on) {
$('#ajax-file-input input:file').attr('disabled', true);
} else {
$(#ajax-file-input 'input:file').attr('disabled', false);
}
}
function AsyncFileUpload_CheckExtension(sender, args) {
disableFileUpload(true);
return true;
}
function AsyncFileUpload_OnClientUploadComplete(sender, args) {
disableFileUpload(false);
var data = eval('(' + args.d + ')');
for (var key in data) {
var obj = data[key];
for (var prop in obj) {
console.log(prop + " = " + obj[prop]);
}
}
doThingsWith(data);
}
</script>
<div id="ajax-file-input">
<ajaxToolkit:AsyncFileUpload ID="AsyncFileUpload1"
OnUploadedComplete="AsyncFileUpload_UploadComplete"
OnClientUploadStarted="AsyncFileUpload_CheckExtension"
OnClientUploadComplete="AsyncFileUpload_OnClientUploadComplete"
runat="server" />
</div>
I ended up positioning a semi-transparent png on top of the control and showing and hiding it to make the control innaccesible.
Hope this helps.
function disableFileUpload(on) {
if (on) {
$("#file-disabled").show();
} else {
$("#file-disabled").hide();
}
}
Simple answer is No. I've had similar asyncupload issues just like those ones. My advice is to stay away from him if you need to control upload with a button, add and remove selected files (you will probably need this later on) and use some javascript manipulation.
Search for the SWFUpload, is a flash component that can be integrated with .NET with ease. It offers multiple javascript options and events. :D
Check the following links:
Official site
Demonstration
As far as I know that the only event exposed by AsyncFileUpload is the UploadComplete event and UploadError. There aren't events specifically that expose functionality to manually initiate the upload. Perhaps some trick in JavaScript could do it but I have not seen such a workaround before.
I have an ASP.NET user control (.ascx file). In this user control I want to use a .js file.
So I include <script src="file.js" type"text/javascript"></script> on the page.
However, sometimes I use this user control in a webpage where this same script has already been loaded.
How can I add a <script /> declaration that will render on the page only if the page doesn't already contain another <script /> tag for the same script?
As you are using asp.net, it makes sense to do the check server-side as that will be where you choose to add the reference to the javascript file. From your .ascx file you could register with the following:
this.Page.ClientScript.RegisterClientScriptInclude("GlobalUnqiueKey", UrlOfJavascriptFile);
... from your page you just call the ClientScript object directly:
ClientScript.RegisterClientScriptInclude("GlobalUnqiueKey", UrlOfJavascriptFile);
The 'GlobalUniqueKey' can be any string (I use the url of the javascript file for this too)
If you try to register a script with the same key string, it doesn't do anything. So if you call this in your page, your control or anywhere else, you'll end up with only one reference in your page. The benefit of this is that you can have multiple instances of a control on a page and even though they all try to register the same script, it is only ever done a maximum of one time. And none of them need to worry about the script being already registered.
There is a 'IsClientScriptIncludeRegistered(stringkey)' method which you can use to see if a script has already been included under that key but it seems pretty redundant to do that check before registering as multiple attempts to register do not throw exceptions or cause any other errors.
Doing the check client-side means that, assuming the multiple javascript references are cached by the browser (they may not be), you still have multiple tags and the over head of each one causing some javascript to run. If you had 20 instances of your control on a page, you could get serious issues.
You can do one of two things client side...
Check the dom for the script tag corresponding to the javascript file your want to check
Test a variable or function you know has/will be defined within the javascript file for undefined.
Here's a quick JS function which uses JQuery to do what you need:
function requireOnce(url) {
if (!$("script[src='" + url + "']").length) {
$('head').append("<script type='text/javascript' src='" + url + "'></script>");
}
}
use something like the following:
if(typeof myObjectOrFunctionDecalredInThisScript != 'undefined') {
// code goes here
var myObjectOrFunctionDecalredInThisScript = { };
}
This tests if your object or function already exists and thus prevents redeclaration.
var storePath = [];
function include(path){
if(!storePath[path]){
storePath[path]= true;
var e = document.createElement("script");
e.src = path;
e.type = "text/javascript";
document.getElementsByTagName("head")[0].appendChild(e);
return false;
} }
use this in your main page and call function include with argument javascript file name
As you want to include javascript on one page of your site only.
If you use Asp.net(4.0) then it is very easy.
Just include following code
<script type="text/javascript" scr="filepath and name here"></script>
in ContentPlaceHolder of content page.
#Shimmy I am coming across this after a long while but maybe it might still be useful or at least help other coming from behind. To check if jquery is already loaded do this
<script>window.jQuery || document.write('<script src="js/libs/jquery-1.x.x.min.js"><\/script>')</script>
Don't miss the escape character in the closing script tag in document.write statement.
#mysomic, why did I not think of that. thumbs up
PS: I would have loved to write this right under the line where #Shimmy wrote that jQuery itself is the script he wants to load. But dont know how to write it there. cant see a reply or anything similar link. This may sound dumb but maybe I'm missing something. Pls point it out, anybody?
If you need it from server side, and Page.ClientScript.RegisterClientScriptInclude won't work for you in case script was included by some other way other then Page.ClientScript.RegisterClientScript (for example page.Header.Controls.Add(..), you can use something like this:
static public void AddJsFileToHeadIfNotExists(Page page, string jsRelativePath)
{
foreach (Control ctrl in page.Header.Controls)
{
if (ctrl is HtmlLink htmlLink)
{
if (htmlLink.Href.ToLower().Contains(jsRelativePath.ToLower())) return;
}
if (ctrl is ITextControl textControl
&& (textControl is LiteralControl || textControl is Literal))
{
if (textControl.Text.ToLower().Contains(jsRelativePath.ToLower())) return;
}
if (ctrl is HtmlControl htmlControl)
{
if (htmlControl.Attributes["src"]?.ToUpper()
.Contains(jsRelativePath.ToUpper())) return;
}
}
HtmlGenericControl Include = new HtmlGenericControl("script");
Include.Attributes.Add("type", "text/javascript");
Include.Attributes.Add("src", page.ResolveUrl(jsRelativePath));
page.Header.Controls.Add(Include);
}
I am using Struts 2.1.6 with Dojo plugin, whole app has ajax links (sx:a).
Did anybody succeed to implement back button functionality and linking to certain content?
Does anybody have any experience how to implement? I am planning to implement (if there is no good solution already) something like so:
changing address bar link (adding parameters) with js which I can then read and get proper content and then publish it with notifyTopics.
Or should I just change whole app to use jQuery plugin? Do jQuery has good solutions for back button and linking on ajax pages?
I can think of 2 simple ways off the top of my head:
<s:form action="actionName">
<input type="hidden" value="<s:property value="someProperty"/>" name="someProperty"/>
<input type="hidden" value="<s:property value="someProperty2"/>" name="someProperty2"/>
<s:submit value="Back" />
</s:form>
or
<s:url name="backURL" action="actionName">
<s:param name="someProperty" value="someProperty"/>
<s:param name="someProperty2" value="someProperty2"/>
</s:url>
Back
If you already have query string parameters:
Back
or
<input type="button" value="Back" onclick="javascript.window=document.referrer;"/>
I have tried to use Struts 2 with dojo and implement the back button. You are already way over the head of Struts 2's ajax implementation. They mainly used and wrote it to write simple and quick ajax function calls and is not very well suited for more extensive uses. Plus when you do s:head theme='ajax' tag; struts will import every ajax js file you 'may' need which kills load time.
I would suggest either 1. Learn dojo and use the library independent of struts 2. Or 2. Get jQuery, I was able to implement a back button functionality relatively simple (more so then struts 2 theme='ajax').
Don't know Struts, but have you looked at dojo.undo (0.4.3)?
All my links go through one action which looks for a parameter menuId (of course id of a menu which has to be shown).
From this action, before returning response I set one javascript function to be called:
setBackMenuId(menuId,sometext)
MenuId is the id, sometext is a name of that menu, so browser log history better.
function setBackMenuId(id,subtekst) {
window.location.hash = "menuId="+id;
document.title = subtekst;
selectedHash = document.location.hash;
if(intervalHashSearch == '') {
initializeHashSearch();
}
}
Then, other needed js functions:
function publishLinkTarget() {
var param = window.location.hash;
if(param) {
if(param.indexOf("menuId") > 0) {
var id = param.split("=", 2);
if(id[1]) {
setCookie('backMenuId',id[1],1,false);
setTimeout('publishLayoutContent()', 100);
}
}
}
}
var selectedHash = '';
var intervalHashSearch = '';
function initializeHashSearch() {
intervalHashSearch = window.setInterval('checkHash()', 500);
}
function stopHashSearch() {
window.clearInterval(intervalHashSearch);
intervalHashSearch = '';
}
function checkHash() {
var currentHash = document.location.hash;
if(selectedHash != currentHash) {
selectedHash = currentHash;
publishLinkTarget();
}
}
function publishLayoutContent() {
dojo.event.topic.publish("layoutContentTarget");
}
If you look at it you see, that first it is called 'setBackMenuId', which adds hash and parameter to address bar and changes title, and then remembers this hash, so interval hash search can find out the differrence. Then it initializes this hash search.
'checkHash' is running ever 500 miliseconds and is checking if hash has changed (that means, that back button was pressed, and not a new link was clicked (setBackMenuId sets selectedHash). If true (back/forward button was pressed) function 'publishLinkTarget' is called, which reads the parameters from hash, and if they are ok, first I set a cookie, so I can read it from the HttpServletRequest and find out for which menu id link is. If I am here it means that I have to also publish the content which is made with 'publishLayoutContent'.
In action class (this is MenuAction, method view, the same as published in ) only this is important:
Integer menuId = null;
if(request.getParameter("menuId") != null) {
menuId = Integer.valueOf(request.getParameter("menuId"));
} else {
menuId = getIntCookie("hiddenMenuId");
}
So, if I don't get the menu id from the parameter (link clicked) I get from a cookie (back/forward button).
And JSP with this target:
<s:url var="layoutContentUrl" action="Menu-view" namespace="/public" />
<sx:div showLoadingText="false" indicator="ajaxIndicator"
id="layout-content" href="%{layoutContentUrl}" theme="ajax"
listenTopics="layoutContentTarget" preload="false"
afterNotifyTopics="/ajaxAfter">
</sx:div>
NOTE: This is a special case if you have everything connected through one parameter, but it can be easily extended with other parameters which publish other targets. I will try to make it enough generic to publish it somewhere, but this is (I guess) a long way ahead :)
If you have any question, please post it.