5/04/2011

Injecting CSS / Javascript on your page, revised

A long time ago, I had posted a method to include Javascript in your .ascx skin’s HEAD section. Things have evolved till then, DNN supports JQuery out of the box, and we need an easy way to inject scripts and css stylesheets in our page, taking care that core stylesheets and jquery are loaded BEFORE our scripts, so they can work correctly. This is especially important with JQuery plugins.
 
Latest DNN versions include two placeholder controls on the default.aspx page, with Ids CSS and SCRIPTS accordingly. These are the controls that can hold our scripts and stylesheets after all core stuff has been loaded, and we can have code inside our .ascx skins that uses these controls and injects stuff like jquery plugins or custom stylesheets inside our page’s HEAD section.
 
I’ve written some reusable code to do so, which, in this basic implementation, can be used to control what scripts and stylesheets are loaded per skin. Of course, you can customize it even more to control things even on a tab level.
 
The code has basically two functions, AddCSS and AddJS (what they do is obvious). You must override the Page_Init function and add your calls to the functions there.
 
Here’s the code. You can use it inline inside your .ascx page, preferrably right after your last @Register statement. 
 
 
<script runat="server">
''' <summary>
''' An enum which holds the various type of elements to be
''' injected in the HEAD section of our page
''' </summary>
''' <remarks></remarks>
Private Enum htmlHeadElementType As Integer
css = 1
javascript = 2
End Enum

''' <summary>
''' Add a CSS element to the HEAD section of our page
''' </summary>
''' <param name="csspath">The path to the CSS file</param>
Private Sub AddCSS(ByVal csspath As String)
AddHTMLHeadElement(csspath, htmlHeadElementType.css)
End Sub

''' <summary>
''' Add a JavaScript element to the HEAD section of our page
''' </summary>
''' <param name="jsPath">The path to the js file</param>
Private Sub AddJS(ByVal jsPath As String)
AddHTMLHeadElement(jsPath, htmlHeadElementType.javascript)
End Sub

''' <summary>
''' This is the actual function.
''' It addds the element to the HEAD section of our page.
''' </summary>
''' <param name="elementPath">The path to the file (css, js etc)</param>
''' <param name="elementType">a htmlHeadElementType corresponding
''' to the type of the element
''' we are adding to the HEAD section.
''' </param>
Private Sub AddHTMLHeadElement( _
ByVal elementPath As String _
, ByVal elementType As htmlHeadElementType)

Dim containerControl As Control

Select Case elementType

Case htmlHeadElementType.css

containerControl = Me.Page.FindControl("CSS")

Case htmlHeadElementType.javascript

containerControl = Me.Page.FindControl("SCRIPTS")

End Select

If Not containerControl Is Nothing Then

'Create our generic html control
Dim oLink As HtmlGenericControl

'Decide on what type of element to add
Select Case elementType

Case htmlHeadElementType.css

oLink = New HtmlGenericControl("link")
oLink.Attributes("rel") = "stylesheet"
oLink.Attributes("type") = "text/css"
oLink.Attributes("href") = elementPath


Case htmlHeadElementType.javascript

oLink = New HtmlGenericControl("script")
oLink.Attributes("language") = "javascript"
oLink.Attributes("type") = "text/javascript"
oLink.Attributes("src") = elementPath

End Select

'Add a script reference to the head section
If Not oLink Is Nothing Then
containerControl.Controls.Add(oLink)
End If

End If

End Sub


Private Sub Page_Init( _
ByVal sender As System.Object _
, ByVal e As System.EventArgs _
) Handles MyBase.Init

'Add the various files to the HEAD section of our page.
AddJS("/somepath/myscript.js")
AddCSS("/somepath/mystylesheet.css")
End Sub

</script>



Read more...

3/23/2011

Fix: Points not added to core DNN Maps module

The DNN Maps module is very simple and useful, but you may come across a problem: When you add points they are simply not added (and when you go to “data” later you see nothing on the list). This behaviour has been reported for module version 01.00.09.

 

The fix is very simple and I’m posting it here in order to facilitate anyone who’s been having the same problem:

 

Go to your DNN root folder and find

DesktopModules\Map\Sources\Dotnetnuke.Map.Standard.js

Change the return value in the mapWriteData function as follows.

 

from:

return encodeURIComponent(strvalue)
to:

return strvalue

 

And points will be added normally.

 

You may need to clear your browser’s cache in order to get it working.

Source: DNN Support Forums

Read more...

2/20/2011

Datasprings Dynamic Forms and image upload problems - a fix for specific scenarios

This may not interest lots of people, but if you're using the very powerful Datasprings' Dynamic Forms module, you may come across it.

 

The setup

 

Historically, there have been quite a few bugs with the image upload field in this module, especially when using in combined with the Preview functionality. I had the following setup:

 

Dynamic Forms module with more than one image upload field.

Preview ON

Thumbnail ON

Initial SQL rendering/bind ON

More than one image fields

 

Other attributes for image fields (not that it matters, just describing the full scenario):

Custom image folder

Save filename only

Save system generated unique name

 

I wanted to setup Dynamic Forms to let a user upload, let's say, four images on the same form. I needed to save those in my own database tables and load them from those tables, so I had initial SQL rendering/bind as well as an sql event to save the images back to my custom database table.

 

The problem

 

When you use your own sql query to get data for your form, (at least at versions 03.30 to 03.40) thumbnails do not get loaded the first time the form is loaded. Instead, you see the full-sized image (after all, that's what stored in the database).

 

If you cause a postback (e.g. by clicking on the "upload new image" link on another image field) then the image url is correctly prefixed with "thumb_" and it is presented in the correct dimensions.

 

The solution

 

To overcome this, I just added code to my select query that would put the "_thumb" prefix in place even if it wasn't there, like the following:

 

SELECT

  field1

, field2

, ...(other fields)

, case

  when ltrim(rtrim(isnull(myfield,''))) =''

  then ''

  else 'thumb_' + myfield

  end as myfield

, ...(other fields)

...(rest of query)

 

So this would always add the thumb_ prefix to the field "myfield".

 

This solved the problem with the initial form rendering. The first time a user sees the form, the thumbnails are shown instead of the full images.

 

The second problem

 

The first solution caused an additional problem: In the case of a postback, as I mentioned before, DF puts its own "thumb_" prefix in the images that are displayed on image fields! So if you attempt to click on "upload new image" in any fields, all the other fields will now have an image starting with "thumb_thumb_", and, since no filename exists that starts with this prefix, broken images will be all you'll see.

 

Please note that you'll see this only if you're using two or more image fields on the same form (or use any other postback that triggers this behaviour).

 

The additional solution

 

I found no way to circumvent this, since it's by design. So I had to resort to JQuery in order to correct the problem. I added a Custom Javascript (you can do that in the Module Configuration section) like the following one:

 

$(document).ready(function() {

    $('img[src*="/Portals/0/imageuploadfolder/"]').each(function(i,d) {
   
        $(d).attr("src",
                 $(d).attr("src").replace("thumb_thumb_", "thumb_")
                 );

    });
});

What it does is take all images that have an src attribute that contains the path /portals/0/imageuploadfolder (replace this with your path) and changes any double "thumb_" prefixes to a single one.

 

This dual workaround seems to work good so far. Now I know this is a very special situation (Dynamic Forms with more than one image field and initial sql rendering) but I hope someone benefits from this. Take care.

Read more...

12/09/2010

Hide those help icons, site-wide

Admit it. Those little questionmark help icons are ugly. Even if you replace the icon image with something more fancy, you still have to display them everywhere, even at the front-end (for example, the Feedback module uses them). And, if you’re developing for more than one language, that creates an awful lot of work for you, since you have to translate all texts that appear as tooltips when you hover over the icons.

 

There’s an easy way to hide them, though, and it’s site-wide. It will also apply to any third-party modules that use the dnn label control for providing labels for form controls.

 

Go to controls\labelcontrol.ascx and change the following line:

   <asp:image id="imgHelp"  runat="server" imageurl="~/images/help.gif" enableviewstate="False" />

 

to

   <asp:image visible="false" id="imgHelp"  runat="server" imageurl="~/images/help.gif" enableviewstate="False" />

 

That’s right. A visible=”false” attribute and the icons will disappear.

 

Also, if you notice that the labels are shifted to the left a bit (especially in IE), then go two lines above and change the line:

 

<span style="width:15px">

to

<span style="width:0px">

 

Or delete the span tag all together.

 

You can find a relevant thread on DotNetNuke.com with some more tips and tricks for the labelcontrol.ascx file here.

 

Have fun!

Read more...

11/17/2010

Using resource files in embedded user controls in skins

If you are editing ASCX skins, then you have probably been tempted to introduce a bit of reusability by creating custom user controls and embedding them to your skins. These can actually function like skin objects, but without all the fuss. I’ve found it easy to create a couple of those in cases where I may need, for example, a common footer for all my skins, but of course you can do a great deal more with them, like introduce functionality without having to copy all the code to every skin file you make.

 

In any case, you have to register your control inside your skin file like this (names of folders, portal and skin path and tagprefix/name are arbitrary):

 

<%@ Register TagPrefix="MyCompany" TagName="FOOTER" src="~/Portals/0/Skins/myskin/myskinobjectsfolder/footer.ascx" %>

 

And use it in your skin file like this:

 

<MyCompany:FOOTER id="myFooterId" runat="server" />

 

The question is: How do you localize these little bastards? Even if you add DNN controls on them, the ResourceKey property continues to get data only from the skin’s resource file, not the control’s.

 

Of course, you can use your skin’s resource file to get localized strings and other data but this isn’t very convenient since you’ll have to add data to every resource file corresponding to each one of your skins. And I’m not going to talk about app_globalresources either! :)

 

(If you don’t know that you can have resource files for your skins, yes, you can. You can have a skins/app_localresources folder and you can have .resx files there  and you can use the ResourceKey attribute in your DNN control tags to get localized data for your controls in your skins).

Well, based on this excellent post here about localizing embedded controls, I thought it would be good to summarize and simplify the process a bit. So here it goes:

 

You can give an embedded user control its own resource file by following two different approaches:

 

1. Make it inherit PortalModuleBase

2. Specify the resource file yourself and make the control use it.

 

Approach 1: Inheriting PortalModuleBase

You just have to add:

 

Inherits="DotNetNuke.Entities.Modules.PortalModuleBase"

 

in your control’s header.

E.g.

 

<%@ Control language="vb" AutoEventWireup="false" Explicit="True" Inherits="DotNetNuke.Entities.Modules.PortalModuleBase" %>

 

Doing so will give you access to the LocalResourceFile property, which will in turn point to the .resx file located in the app_localresources folder right under your control’s location.

 

But what .resx file, exactly? Not the one you expected.

 

The resx file will NOT follow the file name of your user control, but the ID by which it is called. If your control’s filename is MyFooter.ascx, and you call it with an ID of “myFooterId” then your resource file must be named “myFooterId.ascx.resx”.

Then, you can use code like this to get a localized value, for example, for the “Footer.Text” key:

 

<%=DotNetNuke.Services.Localization.Localization.GetString("Footer.Text", LocalResourceFile)

%>

Ugly, because you have to remember to always use the same ID and also because you can’t use two instances of your control in the same skin. Also, inheriting PortalModuleBase restricts your coding flexibility.

 

Approach 2: Forcing the user control to use a resource file

 

If you don’t want your user control to inherit PortalModuleBase but you still need it to have its own resource files, you can define a LocalResourceFile property yourself. This is written directly in the .ASCX file but, of course, you can put it in code-behind too. (sorry about the line breaks):

 

<script runat="server">
Public ReadOnly Property LocalResourceFile As String
     Get
         Return Me.TemplateSourceDirectory & _
     "/" & _
DotNetNuke.Services.Localization.Localization.LocalResourceDirectory & _
     "/" & _
     System.IO.Path.GetFileNameWithoutExtension(me.AppRelativeVirtualPath)
     End Get
End Property

Public Function GetLocalizedString ( _

ByVal key as string) as string
    Dim retVal as String
    retVal = _
DotNetNuke.Services.Localization.Localization.GetString( _
    key, LocalResourceFile)
    return (retVal)
End Function

</script>

 

What we did here was to create our own LocalResourceFile property. This tells the control to use a resource file that follows the file name of the control itself. So, if your control is named footer.ascx, your resource file will lie in app_localresources\footer.ascx.resx. If you need to specify a differently – named resource file, you just replace the GetFileNameWithoutExtension call with your own string. E.g. if you use “myownFooter” there, then there must be a myownFooter.ascx.resx file in the app_localresources folder.

 

In addition, we created a GetLocalizedString function which will just take the key and return the string from the resource file.

We can use this function in our user control to get localized data as follows:

 

<%=GetLocalizedString("Footer.Text")%>

 

This is much simpler and much more flexible.

Read more...

11/04/2010

How to quickly switch your "localhost" binding to another dnn site

Warning: This is for advanced users only.

 

The situation

  • You've got a development machine which has more than one DNN sites that you are currently working on.
  • You're using host headers on those sites to have multiple root-level DNN sites. (And you know the terms "host header" and "binding")
  • You are using IIS7 or higher with Windows 7 or Windows 2008. (I suspect it may work on Vista too, but who's still got Vista? :) ).

(Don't continue reading if the above don't match your case. One of the most common reasons for the above scenario could be that there are root-level urls inside your DNN site, generated by custom modules or just hard-coded into skins / modules, so you have to make your development PC believe that this is a root-level site and not a virtual directory of your default web site. Or, even worse, if you're working on a copy of a site that's already online and there are links and other stuff pointing to the WHOLE domain, you may have to alter your hosts file to make your PC believe that your local site is running on the exact same domain. If you're dealing with DNN a lot, you've probably seen that we don't live in a perfect world and such cases do exist.)

 

 

The problem

You need to configure some modules on your installations, but the modules you have installed can run without limits only on "localhost". Furthermore, some modules, (for example, IndooGrid) need you to buy a licence if you run them ANYWHERE else than localhost - even if it's a test / development machine. But you don't want to buy additional licences since you can do your development work on localhost.

 

What you need

You need a way to quickly switch between what you see when you type "localhost" on your PC's browser, so that you can work on the site of your choice each time.

 

Of course, you can do it by altering your sites' host headers in IIS but that wouldn't be quick. And, I won't say a word about using different virtual machines, each with one localhost site - I've seen it happen. :)

 

The solution

Let's suppose I've got two sites, let's say Site1 and Site2. These listen to their respective host headers, let's say site1.mypc.local and site2.mypc.local. You have configured your Site1 to listen to "localhost"  too. Currently, when you type "localhost" on your browser you see Site1.

 

Now, you either have configured your IIS to have an extra host header (*:80) for Site 1 or you have changed the location of your "default web site" to point to Site1's folder on your hard disk.

 

Do the following:

 

1. STOP your default web site (if that's what you're using to do the trick).

 

2. Go to all the sites that you need to implement the "switching" on, and add a portal alias for localhost so that they can respond to the call (if there is not one already). If you do so, an IISRESET would be good too, in order to avoid the dreaded "redirect loop" error that can happen from time to time.

 

3. Create a batch file with the following and save it as changehost.bat (or any name you choose):

 

@echo off

if "%1" == "site1" goto site1
if "%1" == "site2" goto site2
if "%1" == "" goto error

:site1
%windir%\System32\inetsrv\appcmd set site /site.name:site2 /-bindings.[protocol='http',bindingInformation='*:80:']
%windir%\System32\inetsrv\appcmd set site /site.name:site1 /+bindings.[protocol='http',bindingInformation='*:80:']
goto end

:site2
%windir%\System32\inetsrv\appcmd set site /site.name:site1 /-bindings.[protocol='http',bindingInformation='*:80:']
%windir%\System32\inetsrv\appcmd set site /site.name:site2 /+bindings.[protocol='http',bindingInformation='*:80:']
goto end

:error
@echo You have to provide a parameter!

:end

(Sorry for the wrapping, each call to appcmd should be on a single line.)

 

Let's explain what this batch file does. You call it like this (make sure you have administrator rights):

 

changehost site2

 

And you expect to see your "site2" responding to localhost instead of "site1".

 

What the batch file does is go to the matching section of the batch code, following the IFs. When it gets there, it does two things:

 

First, it uses appcmd.exe (an utility located in your windows directory\system32\inetsrv folder, which allows you to access several IIS properties from the command line) to add a binding to localhost (*:80) to the site you need and REMOVE this binding from any other of your sites that possibly has it. Essentially, it does what you would do by hand - go to iis, add a binding to the site of your choice, remove the binding from the previous site since you are not allowed to have two sites with the same host header.

 

The result is that you will always have one and only one site bound to localhost, and that you can change what site this is by just executing your batch file.

 

Some things to have in mind:

 

The parameter's values can be anything you want, as long as there is a section inside the batch file you can GOTO if you match a parameter value. I've used the same names as the names of the sites in order to have some consistency, but this isn't needed. "Foo" and "Poo" would do the same job, as long as there was a "Foo" section for site1 and a "poo" section for site2.

 

The /site.name switch needs the actual site's name as it is declared in IIS.

 

You must FIRST remove any possible localhost bindings (/-bindings switch) and add the localhost binding (*:80) last (/+bindings switch), otherwise you'll get an error and the binding won't be added because it will exist on another site.

 

If a site does not have a localhost binding, you'll get a message that it can't be found, like this one:

 

ERROR ( message:Cannot find requested collection element. )

Don't be alarmed, it's normal. Appcmd just tried to remove a non-existent binding, no problem.

 

If you try to run this script twice for the same site, you'll also get an error like this:

 

Cannot add duplicate collection entry of type 'binding' with combined key attributes 'protocol,bindingInformation' respectively set to 'http, *:80:'. )

 

This means that you tried to add a binding to *:80 to the site that already had it. That's perfectly normal, too, nothing to worry about, no changes will be made.

 

The example is for two sites, if you have three you'll have to adjust the number of sections, your IF statements and your appcmd calls accordingly. Remember, we're doing a simple thing: Attempting to remove localhost bindings from sites that may have it (only one will) and add a localhost binding to the site we want.

 

I hope this helps a bit. It worked for me, if anyone tries it, please let me know if you have succeeded.

 

Standard disclaimer / warning: Even if you are an advanced user, please use this information at your own risk. It's easy to mess things up (actually, the only thing you'll mess up is your bindings - you won't lose any data but it's enough to make your development sites unaccessible if you don't know what you're doing). I cannot be held responsible if any of the above information proves misleading or incorrect.

 

Happy localhosting! :)

Read more...

10/22/2010

SupportedFeatures field, Search Indexer exceptions, oh my.

I had one of my usual strange problems tonight. The search indexer on a DNN web site I created some months ago would not index the entire site. I got suspicious and checked the event log, where there were pages after pages of general exceptions.

 

Most errors seemed to be something like:

System.ArgumentNullException: Value cannot be null. Parameter name: type at System.Activator.CreateInstance(Type type, Boolean nonPublic) at DotNetNuke.Framework.Reflection.CreateObject(String TypeName, String CacheKey) at DotNetNuke.Services.Search.ModuleIndexer.GetModuleList(Int32 PortalID)

 

Okay, this could not be the real error! I had to find the real cause.

 

First try - cleaning the search index in case it's been corrupted

 

The first thing I tried (and I advice you to do that before you try anything else) was to start with a clean search index, in case it was somehow corrupted. What you can do is the following (taken from this post):

 

If by accident the search index got corrupted, there will be no serach results displayed any further and you have to delete the search index tables manually. Follow these steps:

Login as Superuser ("host" by default)
In Host Menu select item "SQL"
Copy the following 4 lines and paste them into the text box:
truncate table {databaseOwner}{objectQualifier}SearchItemWordPosition
DELETE {databaseOwner}{objectQualifier}SearchItemWord
DELETE {databaseOwner}{objectQualifier}SearchWord
DELETE {databaseOwner}{objectQualifier}SearchItem
Activate the check box and hit Run
The search index will be rebuilt automatically by the scheduler.

 

(Although this is the right way, I've found that a DELETE {databaseOwner}{objectQualifier}SearchItem will be sufficient. If you don't have a special db owner or a specific object qualifier, a DELETE from SearchItem will do the same job, either on your SQL Server Management Studio or your Host-SQL page).

 

Also, you don't have to wait for the scheduler. Go to host-search admin and rebuild the index yourself.

 

Second try - checking and correcting the SupportedFeatures field on the DesktopModules table

 

Well, starting with a clean index didn't help, so I searched further. What I found surprised me. Let's see what this post says:

 

It seems that the desktopmodules table has a supportedfeatures field that should be set to 0 or higher.  But sometimes when modules are installed they end up with a -1 so this causes the error.  Setting the fields to 0 when they are -1 fixes it, like so:

UPDATE [databasename].[dbo].[DesktopModules]
   SET [SupportedFeatures] = 0
WHERE [SupportedFeatures] = -1

This is for SQL Server Express... don't know how it would work in the SQL page of DNN.

 

The solution

 

Well, I did a check on my own DesktopModules table. And suprise, surprise, two modules had a value of -1 for the SupportedFeatures field. As expected, these were the two modules that were generating the exceptions and blocked the indexer from indexing the whole site.

 

I did what the post said, and kaboom! Indexer worked perfectly, no more exceptions.

 

What the SupportedFeatures field actually does

 

What troubled me was what this field actually represents. I had values of 6, 7, 0 etc in other modules' records. So I dug a bit further and found this post:

 

This is a bit field where

2^0 = 1 indicates IPortable

2^1 = 2 indicates iSearchable

2^2 = 4 indicates iUpgradable

add those values for  combinations (i.e. 7 = 1 +2+4 = IPortable,iSearchable and iUpgradable)

 

So this solved the mystery. Those two modules were installed in a wrong way and got an invalid value of -1 for their SupportedFeatures field, which in turn caused the exceptions when the Search Indexer ran.

 

Where and when can this happen?

 

My DNN site was version 5.2.2, but as far as I understand this thing can happen on various DNN versions, depending on the modules installed and how well the installation process is executed. I can't say that exceptions from the Search Indexer are always due to this particular problem, so use this proposed solution with extreme caution if it happens to you, and always have a database backup handy!

Read more...
Related Posts with Thumbnails

Recent Comments

Free DotNetNuke Stuff

Free DotNet Videos

  © Blogger template The Professional Template by Ourblogtemplates.com 2008

Back to TOP