9/30/2013

Displaying User Profile photos in other places (with a little help)

Today I ran into a problem I didn't imagine still existed (in DNN v.7.2). I was using Ventrian Property Agent and I needed to display the profile photo of the user who made the submission inside the module's View Item template. I was surprised that neither Property Agent supported that, nor was there another way to get the user's profile image at a decent size inside the module's templates.

 

I'm sure that even if you aren't using this specific module, you've also ran into this issue one way or another. There is no decent built-in way to display a user's profile image outside of the user profile page itself.

 

In my case, there is a token Ventrian Property Agent users named [USERID] that provides the id of the user who has created the current entry. With only this in hand, I had to find a way to use it to get a decent profile picture.

 

First option would be to use the standard DNN /profilepic.ashx image handler, but this one returns distorted, grainy images of max 64x64 pixels. Using it like this: <img src="/profilepic.ashx?userid=[USERID]"> would give me a crappy image, but at least an image.

 

Two minutes before I was ready to give up, I ran into this gem: http://bbimagehandler.codeplex.com/ - this is a generic image handler that does a lot of things - resizing, watermarks, etc. but one interesting feature is that it can render DNN user profile images - in any dimension given, and with awesome quality compared to DNN's own profilepic.ashx.

 

Give it a try if you're facing the same problem. Have in mind that in order for DNN Profile Pictures to work, you'll HAVE to specify the "db" parameter as mentioned in the documentation. By specifying this parameter, it is implied that you have already updated your web.config file with this key:

 

<add key ="BBDatabase" value="Connectionstring=SiteSqlServer;table=MyImages;ImageField=ImageData;idField=ImageID" />

 

The trick is that you only need the first part if you only need BBImageHandler to handle profile images, since it already knows what tables and fields to use. So you can use this version:

 

<add key ="BBDatabase" value ="Connectionstring=SiteSqlServer;" />

 

Where "SiteSqlServer" is your connection string's name (in case you have renamed it from the default name DNN gives it).

 

This way, it is only enough to know the user id whose profile image you need to show - In my case, as I said above, I had the [USERID] token, so my code was as follows:

 

<img src="/bbimagehandler.ashx?db=BBDatabase&userid=[USERID]&portalid=0&width=300">

 

And voila! A profile image with a 300px width inside a Property Agent Template! (I'm thinking of even using it inside the user profile page itself).

 

Until next time!

Read more...

9/04/2013

Customizing DNN 7 Search and Search Results

DNN 7 has introduced a new, Lucene-powered indexing and search results retrieval mechanism. This means that all techniques you knew are not in effect any more. No more SQL queries and/or tables, data get returned in JSON format and rendered via Javascript. For more details, please read this post on DNNSofware.com

 

This adds a whole lot of new functionality but also a lot of limitations in the way Search Results are presented. Actually, there never was a straightforward way to customize Search Results, but until now you could just edit SearchResults.ascx and its corresponding code-behind file and have your desired result.

 

Things are still ugly regarding customization. The default Search Results layout includes a link guiding you to advanced search concepts, an "Advanced" form that asks you to provide various information such as where you want to search (categorized by module type) and so forth.

 

This doesn't always make sense to end users, and some elements have to be removed in specific scenarios. Users don't always want advanced search options, nor do they need to know what type of modules your site contains. In some cases they don't give a damn about the author, or the last modified date too. But there's no immediate way to configure what will be displayed and what will be not.

 

Moreover, until now you could face the problem of default module titles (which users usually leave as is because they are using containers that didn't include the module title) by tweaking the way module titles were inserted in the SearchItem table (see this older post for more). Since no SQL tables are used any more, you're left with ugly "Text/HTML" and other default titles in Search Results. The JSON returned for each search result item just contains a title that is often made of a concatenation of the tab's name and the module's title (or, in the case of grouped results, just the module's title).

 

Moreover, having a module with a BLANK title is no longer an option since modules with blank titles are not indexed(!). For more info, see my question here: http://www.dnnsoftware.com/answers/cid/420779

 

So le't see how we can address all of the above with some modifications on specific files. Be warned that what I'm saying here applies to version 7.1 and may or may not be in effect for future DNN versions, as well as that those changes are not upgrade-proof, meaning that if you upgrade your site to a higher DNN version you may lose them. But, it's better than nothing. :)

 

Be sure to get a backup of all the files you are going to modify in case something goes wrong!

 

Let's start.

 

Hiding the "advanced" link

File: dnn.SearchResult.js

Location: DesktopModules\Admin\SearchResults

 

Go to line 343, you will see a call to the DnnSearchBox like the following


      dnn.searchResult.searchInput = $('#dnnSearchResult_dnnSearchBox').dnnSearchBox({
           id: 'dnnSearchResult_dnnSearchBox',
           filterText: dnn.searchResult.defaultSettings.advancedText,
           showAdvanced: true,
           advancedId: 'dnnSearchResultAdvancedForm',

...

 

Locate this line:

showAdvanced: true,

 

And change it to

showAdvanced: false,

 

This is an option that you can't access via the module settings and it hides the "advanced" link. Unfortunately, the form that would pop up will still be visible at the bottom of the page so you have to take an extra step:

 

File: SearchResults.ascx

Location: DesktopModules\Admin\SearchResults

 

Go to around line 55 and you'll see a DIV element surrounding the elements of the Advanced form, like the following:

<div id="dnnSearchResultAdvancedForm" class="dnnForm">
    <div class="dnnFormItem">
        <dnn:Label ID="lblAdvancedTags" runat="server" ResourceKey="lblAdvancedTags" />
        <input type="text" id="advancedTagsCtrl" />
    </div>
    <div class="dnnFormItem">

...

Add a style="display:none;" attribute to the outer div to hide the whole form.

 

If you need the advanced form but do not need to show specific controls, you can just skip the steps above, go at this point and just add a style="display:none;" attribute to any of the DIVs nested inside to hide the specific control you don't need (e.g. Scope)

 

Hide Advanced Tips link

 

File: dnn.SearchResult.js

Location: DesktopModules\Admin\SearchResults

 

Go to line 359 (it's empty), just above this piece of code:

$('a.dnnSearchResultAdvancedTip').on('click', function () {
            $('#dnnSearchResult-advancedTipContainer').slideToggle('fast');
            return false;
        });

And insert the following line:

$('a.dnnSearchResultAdvancedTip').hide();

 

Alternatively, you can achieve the same result by adding a style="display:none;" attribute at the A tag in SearchResults.ascx, line 6:

<a href="javascript:void(0)" class="dnnSearchResultAdvancedTip"><%= LinkAdvancedTipText %></a>

 

Hide Results by Page / Sort section

 

File: SearchResults.ascx

Location: DesktopModules\Admin\SearchResults

 

Go to line 13 and add a style="display:none;" attribute to the outer DIV element there (first element in code snippet below):

<div class="dnnSearchResultPanel">
    <div class="dnnRight">
        <ul class="dnnSearchResultSortOptions">
            <li class="active"><a href="#byRelevance"><%= RelevanceText %></a></li>
            <li><a href="#byDate"><%= DateText %></a></li>...

 

If you hide this section but you need to specify a different sort order and/or results per page setting, you can alter the Javascript call at the same file, line 116 onwards, by altering the sortOption and pageSize initial values:

$(function () {
        if(typeof dnn != 'undefined' && dnn.searchResult){
            dnn.searchResult.moduleId = <%= ModuleId %>;
            dnn.searchResult.queryOptions = {
                searchTerm: '<%= SearchTerm %>',
                sortOption: 0,
                pageIndex: 1,
                pageSize: 15
            };

sortOption can be 0 for date, 1 for relevance.

pageSize can be anything you need.

 

Hide various elements on Search Results

 

Well, some people don't like the "last updated" information. Others don't like the "author" info. So let's see how we get rid of anything we don't need:

 

File: dnn.SearchResult.js

Location: DesktopModules\Admin\SearchResults

 

Start from about line 112,where you'll see code like this:

markup += '<div class="dnnSearchResultItem-Others">';
        markup += '<span>' + dnn.searchResult.defaultSettings.lastModifiedText + ' </span>';
        markup += data.DisplayModifiedTime;
        markup += '</div>';

        markup += '<div class="dnnSearchResultItem-Others">';
        markup += '<span>' + dnn.searchResult.defaultSettings.sourceText + ' </span>';

Comment lines 112 to 115 to get rid of the "last updated" text

Comment lines 117 to 119 to get rid of the "source" text and link

Comment lines 121 to 123 to get rid of author info

Comment lines 126 to 135 to get rid of 'tags" info

 

Hide default HTML module titles (if any)

 

As mentioned earlier, you may not want module titles to appear in search results, but they do, even if your container doesn't display the module's title. The most common is "Text/HTML" and we'll see how to get rid of it in Search Results.

 

File: dnn.SearchResult.js

Location: DesktopModules\Admin\SearchResults

 

Scroll down to the bottom of the file and append this function at the end:

function fixTitle (s) {
   
    if (s=='Text/HTML') {
        return ('...');
    }
   
    var s1;
    var s2;
   
    s1=s.substring(0,s.indexOf('>')-1);
    s2=s.substring(s.indexOf('>')+2, s.length);
   
    if (s2=='Text/HTML' || s2==s1) {
        return (s1);
    }
    else
    {
        return (s);
    }
}

This function accepts a search result item title and first checks if the title is "Text/HTML". If so, it returns three dots ("...") instead. You will find this in grouped results, where the module title is displayed on its own.

 

If the title does not belong to a group subset, it'll be in the format "xxx > yyy" where xxx is the page's name and yyy is the module's title. The code breaks the string in its two parts and checks if the second part is equal to "Text/HTML". If so, it returns only the first part (the page's name). Also, if both parts are the same it once more returns the first part. Useful when a module has the same title as the page and you don't want to see this in search results.

 

This is not the best possible solution, but it's a decent workaround, provided that you don't have the character ">" anywhere in your module titles or page names. I know it can get better, but this is only to demonstrate how you can do it.

 

In order to put our function into effect, we have to go to line 107:

markup += '<a href="' + data.DocumentUrl + '"' + dnn.searchResult.defaultSettings.linkTarget + '>' + data.Title + '</a></div>';

and replace data.Title with fixTitle(data.Title):

markup += '<a href="' + data.DocumentUrl + '"' + dnn.searchResult.defaultSettings.linkTarget + '>' + fixTitle(data.Title) + '</a></div>';

Hide subsets (grouped results)

If, for some reason, you don't want to have any grouped results then you can comment out the code that is generating them. Be adviced that the result that was supposed to be grouped won't display a description underneath, just the title and link.

 

File: dnn.SearchResult.js

Location: DesktopModules\Admin\SearchResults

 

Go to line 159 and comment the code there until line 161:

// render subsets
                   for (var j = 0; j < result.Results.length; j++) {
                       markup += '<div class="dnnSearchResultItem-Subset">' + dnn.searchResult.renderResult(result.Results[j]) + '</div>';
                   }

 

Finally, if you tamper with the way search results are rendered, you may not want to have auto-search (search-as-you-type) get in your way by rendering everything in a different way (e.g. using default text/html module titles). There is no option for disabling it, but we can always hack the code a little more:

 

File: SearchSkinObjectPreview.js

Location: Resources\search

 

Go to line 137 and comment out this piece of code (until line 155):

throttle = setTimeout(function() {
                         var service = $.dnnSF ? $.dnnSF(-1) : null;
                         var url = makeUrl(val, service);
                         if (url) {
                             $.ajax({
                                 url: url,
                                 beforeSend: service ? service.setModuleHeaders : null,
                                 success: function(result) {
                                     if (result)
                                         generatePreviewTemplate(result);
                                 },
                                 error: function() {
                                 },
                                 type: 'GET',
                                 dataType: 'json',
                                 contentType: "application/json"
                             });
                         }
                     }, self.settings.delayTriggerAutoSearch);

This will prevent the Search skin object from auto-searching.

 

Well, that's all! Even if line numbers change with an upgrade, you have a reference point on what to seek in code. I know this is not the prettiest or the most accurate and bullet-proof solution in the world, but until DNN Corp. decides to add more configuration settings to its Search subsystem, it will have to do. In the meantime, you can vote for my relevant suggestion in Community Voice here:

http://www.dnnsoftware.com/voice/cid/420823

 

Oh, and please feel free to suggest any other improvements / hacks to Search and Search Results! I know I didn't cover the whole range of possible modifications, but these are, IMHO, the most common ones.

 

Until next time!

Read more...

7/22/2013

DNN Sharp's My Tokens 30% off (until 7/26/2013)

da12dcee5605e1fa99b50d8b9401aedcDNN Sharp are promoting a discount on their popular My Tokens module that will expire on the 26th of July, 2013. To get the discount, you have to buy their Action Form module from the DNN Store.

 

Action Form is a form builder module which lets you create complex forms quickly and save data to the database / send emails etc. on submission.

 

My Tokens is a module which lets you include dynamic content from database or HTTP requests in static content.It works with a number of DNN modules out of the box (including Text/HTML and Form and List) but you can even patch the DNN core (with instructions) to extend support to every installed module.

 

 

Buy Action Form here

Buy My Tokens here

Read more...

7/20/2013

25% off all Data Springs products (until 7/23/2013)

logoData Springs is taking 25% off all its products until Tuesday, 7/23/2013. You can order your products from the DNN Store and use the promo code BYEANNIE on checkout to get a 25% discount.

 

If you don't know Data Springs' modules, you should give it a look - their flagship products are Dynamic Forms, Dynamic Registration and Dynamic Login, and there are more than a dozen more. There is also a bundle named Data Springs Collection for a very low price compared to bying the products one by one.

Read more...

7/17/2013

At last! DNN7.1 has extensionless URLs and a "url name" setting for every page

This is a big step forward. Starting with DNN v.7.1, you can say goodbye to ugly URLs and .aspx extensions. Following a suggestion made by yours truly (here: http://www.dnnsoftware.com/voice/cid/138237), DNN 7.1 has adopted a "Page URL" field in Page Settings, letting you specify the exact URL the page should use.

 

My Website   About Us   StyleAndGuide

 

This is essentially an URL rewrite for the page, that makes the page's URL independent of the page's name, meaning that you can change the name as many times as you like, without affecting any links that are present in text/html modules or elsewhere.

 

The only bad thing (or good, depending on your point of view) is that the URL is not automatically synthesized based on page hierarhcy, meaning that you have to specify the exact URL for each page, starting from the virtual root.

 

For example, in the default DNN installation you have the About Us page which has a URL of "/About-Us" while the sub-page Style Guide has a URL of "/About-Us/StyleGuide". The whole URL for the Style Guide page has to be typed again, i.e. the "/About-Us" part is not inherited from the URL field of the parent page.

 

This makes things a bit more difficult since you have to remember the URLs of parent pages if you want to represent the hierarchy using friendly URLs, but on the other hand gives you a bit of freedom since you do not need to specify exact paths to get to a page - you can easily give the Style Guide page, for example, a URL of "/YourStyleGuide", forgetting the "About-Us" part at all.

 

It would be nice to see an "inherit" option in future versions though.

Read more...

6/27/2013

Bug: "Redirect after login" not redirecting to localized versions of page

I am currently working on DNN7, on a multilingual site (4 languages). The site has public registration and users must be redirected to a specific page after they complete their registration.

 

I found that DNN would not let me specify a different page for every language - in fact, if I went to Admin -> Site Settings -> User Account Settings -> Login Settings -> Redirect after Registration and selected a specific page in one language, it would give me <None Specified> if I went to the setting using another language.

 

This would probably have no impact if DNN took care to redirect the user to the localized version of the page after registration - which it doesn't (at least until version 7.0.6). So I had to alter a few things, which I'd like to share with you in case you are facing the same problem.

 

The problem seems to be on the \DesktopModules\Admin\Security\Register.ascx.cs file, which fortunately can be directly edited so no recompilation is needed. If you go to line 162 there, (the "else" portion of a rather large if statement), you'll see that it does the following:

 

_RedirectURL =

Globals.NavigateURL(Convert.ToInt32(setting));

 

Where "setting" is the Tab ID of the tab you have selected on the Site Settings section for redirecting after registration. This, of course, is not localized so it'll always take you the the actual page you have specified - regardless of the actual language the user is using to register.

 

In order to fix this, I copied and adapted some code from the language skin object (yes, the language picker) which can always find the right localized version of a page. So here's what I did:

 

I changed the line:

 

_RedirectURL =

Globals.NavigateURL(Convert.ToInt32(setting));

 

to:

 

int currentPortal =

PortalController.GetCurrentPortalSettings().PortalId;

Locale currentLocale =

LocaleController.Instance.GetCurrentLocale (currentPortal);

TabInfo localizedTab =

new TabController().GetTabByCulture(

Convert.ToInt32(setting), currentPortal, currentLocale);


_RedirectURL = Globals.NavigateURL(

Convert.ToInt32(localizedTab.KeyID));

 

And I also had to add two usings on top of the page:

 

using DotNetNuke.Entities.Tabs;
using DotNetNuke.Entities.Portals;

With those changes, I managed to have the page to redirect to specified only once and have DNN redirect me to the page's localized version depending on the language I used to register.

 

Also please see here: http://support.dotnetnuke.com/project/All/2/item/26617 for other issues with Redirect after Login functionality. I applied the change to line 136 of the file mentioned in this issue, too.

 

One caveat: If you upgrade your DNN installation, you will probably lose those changes.

 

Also, have in mind that the same thing probably applies to the "Redirect after Login" functionality - I'll probably post something similar about that soon :). Plus, I have also created an SQL-based adaptation of GetTabByCulture to be used with OWS - I hope I have enough time to post this here too.

 

Standard disclaimer: Keep a backup of your initial Register.ascx.cs file. Don't blame me if this doesn't work for you. This is a solution that worked for my own project and it's not guaranteed to work in every case. Did I mention backup?

 

Until next time!

Read more...

6/14/2013

Great news on Open Web Studio (OWS)!

ows-logo

 

If you are using DNN, I'm sure you have at least heard of Open Web Studio, (OWS), a RAD development platform for DNN that is open source, free, and lets you create things for which you would otherwise need full-blown module development. For example: Database - bound grids, forms, queries and templates to view results, AJAX calls, conditional display of information and much more.

 

In the past few years, OWS has been stuck to version 2, with frequent updates including updates for working with DNN 7, but no new functionality. Although the community is very active, documentation is still lacking and many people keep asking about the future of OWS, worrying that it may be (or has already been) abandoned.

 

I had a recent chat with Kevin Schreiner, the lead developer for OWS, and with his permission I'm copying here his thoughts and plans on OWS. If you don't want to read all of it, bottom line is that Kevin is actively supporting OWS and preparing for OWS 3!

 

Here's what I asked, among other things:

 

I am curious (just as so many people) to learn news about OWS's future (if such a future exists). I've used it so much I have lost count - and I'm eager to see it evolve. As far as I understand, you're (or were) the only developer maintaining it. Should I keep my hopes up high or go desperate? :)

 

And here's Kevin's reply:

 

No worries, I understand the anxiety. I created ListX which became OWS while working for Bi4ce - which became R2integrated, and I'm still the only developer maintaining it. This shouldnt be a cause for concern. I've been holding off on releasing OWS3 due to a myriad of complications revolving around the changes to the DNN platform and adjustments to the core libraries needed for the new UI all while keeping the platform releases for OWS2 still working within the latest DNN versions. The issues with the OWS3 UI stem from the fact that DNN which typically struggles to release ANYTHING on newly released technologies, adopted the latest jQuery version in the DNN7 release. Doing this caused the UI Layout Manager plugin which I was relying on for the OWS3 release to malfunction and I've had to resort to building a new logical structure for handling it. Plus, there have been some issues with the logic in use of the UI because it has been confusing while working within multiple configurations and multiple actions simultaneously. You can see from the original comps how this could be confusing, because when you have more than 1 action opened, you dont know which action is linked to which configuration.


Hopefully something that should make the community rest easily, I rely on OWS for 99.9% of my own livelihood, using it as my core development platform, extending its core functionality and keeping it up to date for the latest DNN releases. Obviously, this being the fact means that OWS isnt going anywhere ;)  (...) Once I feel comfortable with the OWS3 release stability, I will most assuredly release it to the general public. It has always been and will always be primary in my focus to release something that is solid and stable as it is so important to all of our runtimes.

 

In the meantime, rest assured that nothing is going to happen to my baby :) It's a labor of love, and something that I personally rely on.

 

I am extremely happy with his answer! What about you?

Read more...

6/06/2013

Bug: Custom profile properties cannot be selected for custom registration form (DNN 7)

DNN 7 allows customization of the registration form via an easy interface, with intellisense. You just select "Custom" for Registration Form Type in Admin - Site Settings - User Account Settings tab and you can add all the fields you like to your registration form.

 

Site Settings

At the moment of writing, this doesn't seem to work well with custom registration properties (i.e. fields you have created yourself via the "Profile Settings - Add New Profile Property button below on the same tab). If you try to type a custom property's name on the Registration Fields box, it just won't come up (although it's been reported that properties of datatype "Text" do come up).

 

In order to make these properties available to your registration form until a fix is applied to this, you can use SSMS (provided that you have access and experience using it) and do the following:

  • Go to your DNN database
  • Open the "PortalSettings" table for edit and find the record that has the value "Registration_RegistrationFields" in the "SettingName" field
  • You'll see that the "SettingValue" field is a comma-delimited list containing the field aliases of the properties that are to be displayed on the custom registration form. Add the aliases of your own fields in the list.
  • Update the table and go to your DNN installation (as the Host user), and click on Tools - Clear Cache
  • If everything goes well, you'll see that the "Registration Fields" box now contains your properties too, and they will appear normally on the registration form.

Another way to do this is to use the Host - SQL section on your DNN installation. To see the current profile properties that are displayed on the custom registration form, type this into the Script box and click "Run Script":

 

SELECT
SettingValue 
FROM  {databaseOwner}{objectQualifier}PortalSettings
WHERE
SettingName = 'Registration_RegistrationFields'

 

In order to add one or more fields at the end of the list, you must run something like this (where 'MyField' is your custom field's alias). Remember the comma and use no spaces.

 

UPDATE  

{databaseOwner}{objectQualifier}PortalSettings
SET
SettingValue = SettingValue + ',MyField'
WHERE
SettingName = 'Registration_RegistrationFields'

 

After you run it, go to Tools and click Clear Cache and you will see your fields on the "Registration Fields" box in the Admin - Site Settings - User Account Settings tab.

 

If you still need more power and customization with your custom registration page, take a look at the Dynamic Registration module from Datasprings - at the moment of writing this, version 5.0 has just been released for DNN 7.x.

 

Standard disclaimer: When you do stuff like the above on your database, you do it at your own risk, and I have no responsibility should you damage your database, your computer, or the universe. Always have a backup handy! :)

 

Until next time!

Read more...

11/06/2012

Removing Google Analytics from your DNN site

It's very easy to add the Google Analytics tracking code to your DotNetNuke site: Just go to Admin - Google Anaytics, enter your tracking ID and the tracking code is automatically generated for every page.

 

But what if you need to REMOVE the Google Analytics code? DNN doesn't let you replace your tracking ID with nothing in the Google Analytics page (the field is mandatory). So, you either stick with it or put an invalid value (such as "0") and have your site generate a totally invalid and useless tracking code all the time.

 

What you can do, is go to your site's root folder and find the siteanalytics.config file. That's where the tracking code is. (As of DNN version 6.2.4, this doesn't seem to have changed).

 

The problem is that if you delete this file it keeps getting regenerated. So the only way to avoid generating analytics is to keep the file but COMMENT OUT or DELETE everything inside the CDATA tag (essentially,the script tag from start to finish). This way, DNN will still see the file is there, but won't generate anything for Google to use.

 

If, at a later time, you need to restore your Google Analytics tracking code, you can either delete the file (so that it can be auto-generated again) or just uncomment what you commented.

 

Hope it helps!

Read more...

11/05/2012

Quick hints: How to ensure your site remembers your visitors' credentials so that they don't have to login every time

If you need to make life easier for your portal's registered users, you may have to make it remember them so that they won't have to retype their credentials next time they visit.

 

Making "remember me" always checked

 

By default, DNN's "remember me" checkbox on the login page is visible and unchecked. In order to make it checked by default, you'll have to edit the /DesktopModules/AuthenticationServices/DNN/Logon.ascx file (assuming DNN 6.2 but probably same in earlier versions) and find the following control:

 

<asp:checkbox id="chkCookie" resourcekey="Remember" runat="server" />

 

Add a checked="true" attribute to the control as follows:

 

<asp:checkbox id="chkCookie" resourcekey="Remember" checked="true" runat="server" />

 

And the checkbox will always be checked by default.

 

If you upgrade your DNN installation sometime in the future, you'll probably lose this setting, so keep in mind that you may have to edit the file again after an upgrade.

 

Defining the "remember me" duration

 

The second thing you should define is for how long a user will be remembered. This is controlled by a key in your web.config file, under the <appSettings> section:

 

<add key="PersistentCookieTimeout" value="0" />

 

The value expresses duration in minutes. So, if you need your portal to remember your users for, let's say, a month, that's 60*24*30 = 43200. A year would be 60*24*365 = 5252600 and so on.

 

It's important to set that value, since your "remember me" functionality may not work at all, especially in older DNN versions that are using an ASP.NET 2.0 application pool. For more details, see here and also here.

 

Ensuring that the "remember me" checkbox is enabled

Finally, if you don't see the "remember login" checkbox at all, it may have been disabled in your DotNetNuke installation. The setting to disable or enable the remember me functionality is under Host Settings - Basic Settings - Host Details - "Enable remember me on login controls" checkbox.

Read more...

11/22/2011

Dealing with ‘Could not load type ‘DotNetNuke.Common.Global’’

There are a thousand reasons this dreaded message can appear, but what if it appears on a site that you are 100% sure nobody has tampered with in any way other than uploading files for quite some time?

The problem

The error itself is as follows:

Parser Error

Description: An error occurred during the parsing of a resource required to service this request. Please review the following specific parse error details and modify your source file appropriately.
Parser Error Message: Could not load type 'DotNetNuke.Common.Global'.
Source Error:
Line 1:  <%@ Application Inherits="DotNetNuke.Common.Global" Language="VB" %>
Source File: /global.asax    Line: 1

The context

That was exactly the case I faced today when a client called me and reported the error. The client was absolutely sure that no changes had been performed other than uploading some files, both to the application and the server itself (no upgrades, no apppool changes, no iis configuration changes).  Just to be safe, I checked the dates of the files in the bin folder as well as the date of the global.asax file and all had not been changed during the past few months.


Possible causes

Searching the Web, I was panicked to see that there were at least a few dozen possible causes of this error. For example, there’s a multi-page thread in the DotNetNuke forums dicussing this error. Sadly, most of the cases regarded upgrades to the core platform, recompiling or other stuff that had been changed.

So what else could be wrong? Well, I once more discovered than when something seems inexplicable, it doesn’t always have to be some extremely complicated technical issue.


Solution

The client had access to the full site root via an FTP client. Upon trying to upload stuff, the client accidentally moved the App_Data folder inside another folder. That was it!

I moved the App_Data folder back to the root and everything was fine again.

What I learned from this incident: Always look for the simplest possible cause of an error first. Hope it helps some of you out there.

Read more...

11/04/2011

What modules I used to develop www.alunet.com (Part 3 – Indoo Grid)

When it comes to displaying data on a grid, you can either choose to implement your own custom module, utilize a module like Open Web Studio (read about it in Part 2) or, if you really want speed and efficiency, install a copy of Indoo Grid.

 

This module delivers what it promises: Grids. Fully customizable, sortable and editable, either in-line or via a form. Of course, if you really need advanced form functionality, have a look at Dynamic Forms from Datasprings (read about this module in Part 1).

 

Every multi-purpose module is powerful in a specific area. Indoo Grid is very powerful in presenting data, and provides acceptable functionality for editing data. It’s ideal, though, for administrative tasks, especially if custom tables are involved. That was the case with alunet.com, so I used it heavily at the site administration pages.

 

indoo_products

Here you can see how the products administration screen looks like. The links to view and edit pages are constructed fields which lead to a Dynamic Forms page and a public detail page respectively. You can sort columns by clicking on them and you can filter data using the text box and the comboboxes above the grid. The “Category”, “Company” and “Views” columns come from respective joined tables. This is not an SQL view you’re seeing – joins are defined in Indoo Grid’s configuration.

 

You can instruct Indoo Grid to display records from a table or view, defining things such as searchboxes (you can define separate searchboxes for each column or combine them in a single searchbox, excluding the columns you don’t need to search), records per page selector, initial sorting and sortable columns, editable columns (if you need to edit things) and so on.

 

You also don’t have to worry about those cryptic referential integrity errors when trying, let’s say, to delete something that is related to another table – you can configure it to display messages that are understandable to the user.

 

indoo_categories

This is the grid for managing categories. Remember, the Categories table is a self-joined table that is presented in the form of a tree inside the web site.

 

You can also create columns that are lookups in joined tables and even automatically create drop-downs from the values of those columns so that you can filter your grid with them. Additionally, you can create “calculated” fields that may contain one or more column values together with other html markup or scripts you may need (yes, that’s an easy way to include pictures or links to other pages).

 

One thing that Indoo Grid does that you won’t easily find elsewhere is that it can also handle hierarchical data. In alunet.com’s case, the “categories” table is a self-joined table that contains the entire tree of categories, where each record “knows” the id of its parent record. This was easily managed with Indoo Grid.

 

indoo_categories_hier_edit

Here you see the same grid for Categories management, after navigating to a specific category (castings). What we see now are the children of this category in the tree. You can also see that we have an easy way to add new records right below the grid, that will belong to the “Castings” parent category.

 

Of coure, you can also use Indoo Grid for your front-end. It’s fully customizable in terms of styling and you can always use it in read-only mode. It’s very fast (authors have a demo with a million records which runs pretty smoothly),and there are some predefined templates and samples on their website, too.

 

Company website: www.indoolab.com

Buy it from Snowcovered: Click

 

Click here to read Part 1 (Introduction and Dynamic Forms from Datasprings)

Click here to read Part 2 (Open Web Studio)

Read more...

10/21/2011

Adding paging to the core Announcements module

The Announcements module (at the time of writing this, the latest version was 4.0.3) is a very simple DNN core module that lets you present a list of titles and descriptions leading to files, other pages, external links or nowhere at all. It’s customizable via templates, but it doesn’t include any form of paging. So if you have like hundreds of announcements to publish, you probably have to use another module.

 

Not any more, since with a little help from a clever JQuery plugin called Pajinate (download from here), you can add paging to your announcements module. Of course, you’ll be loading all items since Pajinate functions on the client side, but it’s better than not having paging at all.

 

I used the default template that comes with the Announcements module and added what was needed to implement paging on it. Unfortunately, Pajinate does not hide the pager when there’s only one page nor does it hide the First/Previous and Next/Last buttons when we are at the first and last page respectively, so I had to write my own little chunk of Javascript to handle this correctly.

 

Additionally, I adapted the default header and footer templates to include IDs and other stuff needed for pajinate to work correctly. Those IDs are used in the additional javascript code I wrote.

 

So here’s the Javascript code. I suggest you put this code on a separate file (I used pajinatehelper.js) and load it AFTER the pajinate javascript file. You will probably need to change the value of the itemsperpage variable (top of script) to your own preference, as well as the literals for the First/Previous/Next/Last buttons that follow.

//Set the number of items per page

 

var itemsperpage = 10;
$(document).ready(function(){
$('#paging_container').pajinate( {
item_container_id : '#pagetable', //This must be left as is
items_per_page: itemsperpage,
nav_label_first: 'First',//Change this to whatever you like
nav_label_prev: 'Prev', //Change this to whatever you like
nav_label_next: 'Next', //Change this to whatever you like
nav_label_last: 'Last' //Change this to whatever you like
});

//Get the total number of items
var total_items = $('#pagetable').children().size()

//Get the number of pages
var numberofpages = Math.ceil(total_items/itemsperpage);

//Since we start at page 1, hide First/Previous buttons
hideFirstPrev();


//Decide what to hide when user clicks on a page number.
$('#paging_container').find('.page_link').click(function(){

var currpage = parseInt($(this).attr('longdesc'));
currpage+=1; //longdesc is 0-based but page numbers start at 1.

if (currpage==1) {
hideFirstPrev();
}

else if (currpage==numberofpages) {
hideNextLast();
}

else

{
showAll();
}

});

//Hide First and Previous links when the user clicks the First link
$('#paging_container').find('.first_link').click(function(){
hideFirstPrev();
});

//Hide Last and Next links when the user clicks on the Last link
$('#paging_container').find('.last_link').click(function(){
hideNextLast();
});

//Decide what to hide when the user clicks on the Next or Previous links
$('#paging_container').find('.previous_link,.next_link').click(function(){

var currpage = parseInt($('#paging_container').find('.active_page').attr('longdesc'));
currpage+=1; //longdesc is 0-based but page numbers start at 1.

if (currpage==1) {
hideFirstPrev();
}
else if (currpage==numberofpages) {
hideNextLast();
}
else {
showAll();
}

});


//If we have only one page, disable pager completely.
if(total_items<itemsperpage){
$('.page_navigation').hide();
}

});

//These are helper functions for hiding / showing First/Last/Next/Previous links

function hideFirstPrev() {
toggleFirstLastNextPrevControls(1);
}

function hideNextLast() {
toggleFirstLastNextPrevControls(2);
}

function showAll() {
toggleFirstLastNextPrevControls(3);
}

function toggleFirstLastNextPrevControls (mode) {

if (mode==1) {
$('#paging_container').find('.first_link').hide();
$('#paging_container').find('.previous_link').hide();
$('#paging_container').find('.last_link').show();
$('#paging_container').find('.next_link').show();
}

else if (mode==2)
{
$('#paging_container').find('.first_link').show();
$('#paging_container').find('.previous_link').show();
$('#paging_container').find('.last_link').hide();
$('#paging_container').find('.next_link').hide();
}

else if (mode==3)

{
$('#paging_container').find('.first_link').show();
$('#paging_container').find('.previous_link').show();
$('#paging_container').find('.last_link').show();
$('#paging_container').find('.next_link').show();
}
}






Here’s the code for the Header Template for the Announcements module:




<link rel="stylesheet" type="text/css" href="/portals/0/scripts/pajinate.css"/>
<script type="text/javascript" src="/portals/0/scripts/jquery.pajinate.js">
</script>
<script type="text/javascript" src="/portals/0/scripts/pajinatehelper.js"></script>

<div id="paging_container" style="text-align:left;">
<table class="DNN_ANN_DesignTable" cellspacing="0" summary="Announcements Design Table" border="0" style="border-collapse:collapse;"><tr><td>
<div id="pagetable">





Please note that I include the stylesheet (pajinate.css), the js file for Pajinate (jquery.pajinate.js – you can use jquery.pajinate.min.js too) and my own .js file (pajinatehelper.js) from my /portals/0/scripts folder (I actually created that “scripts” folder, since it didn’t exist in the default skin’s folder structure). You, of course, can load them from whatever path you like, or you can even inject them in the page’s HEAD section (see this post for more on that).



Pajinate demands that the items to be paged are enclosed inside an element with a specific id (in our case, “paging_container”). You can see the complete documentation here. Unfortunately, this doesn’t work well with tables, so I used an enclosing DIV with this id. Of cource, in your implementation, you can ditch the TABLE alltogether and just use DIVs. I tried to alter the default template as little as possible.



Pajinate also needs a second element that contains elements to be paged. I used this also, as a div with “pagetable” id. Those two are configurable (see the script)



And here’s the code for the footer:




</div>
</td></tr></table>
<div class="page_navigation"></div>
</div>





The “page_navigation” div is once again required by pajinate in order to show the pager.



Phew. When you put all the code in place, and ensure that it’s working correctly, the result will be something like this:



pajinate



The orange pager comes from the default stylesheet that comes with Pajinate. You can, of course, change it at your will. In this example, I used a page size of 2.



CAUTION: Since errors in Javascript can sometimes render your page useless and even disable the edit controls, please take a bookmark of the module edit page URL before you apply changes, so you can always get back to it in case things go wrong.



Have fun and good luck!

Read more...

8/09/2011

What modules I used to develop www.alunet.com (Part 2 - Open Web Studio)

Open Web Studio (or OWS in short) is a FREE (yes, free!) module from R2integrated, formerly known as ListX. (There are paid subscription options too, although I'm not quite sure what is offered). At first glance, it looks like a module where you can define an sql query and then create a template to render results - something like a glorified data repeater. But it's a lot more than that. It supports AJAX, paging, variables, it can "talk" to DNN and retrieve things like the tab id, the module id, the locale etc. it can make decisions and branch execution, you can nest modules and have one call another via AJAX, you can do redirections or change page and module titles, you can cache query results and you can even write to files!

 

Activity lists, product and company lists, product and company details, as well as the users' control panel are rendered via OWS. Additionally, OWS is used in all security checks (such as checking if the user has the right to edit a specific company or product or if the user can add another product). This is achieved via modules that have no front-end but nevertheless run when the page is requested.

 

alunetcom_controlpanel_thumb[9]

alunetcom_controlpanel_products_thumb[11]

alunetcom_companylist_thumb[6]

 

Development in OWS is done via a handy tree-like interface where you define things like variables, If - else sections, sql queries, header-detail-alternate detail-footer sections etc.

 

The site's first page is a good OWS example. The asynchronous effect you will see when you load the first page is because only the main activities are retrieved when the page loads. Subactivities are then queried and rendered via AJAX, and query results are cached for each subactivity (yes, OWS can do named caching on query results so that you don't have to query your database all the time - you can even use fields from your queries to construct names for the cached elements). This is on purpose, since there is a large number of activities and counting products and companies for each one can take some time.

 

What's more is that you can then have more control over your caching - when a new company or product is submitted, you can clear only the cache that corresponds to the specific main activity it belongs to - leaving all the other query results cached. (No, I haven't done that yet - that's why it's a bit slow :))

 

Another example would be the company details page, in the future - the site will soon be supporting "packages", i.e. there will be companies that will register for free and companies that will pay. The "free" companies will have fewer details shown than the paying ones. This is very easy to control via OWS.

 

alunetcom_companyprofile_thumb[16]

 

In fact, I could use OWS to even create the submission forms - but it would be very hard since I would have to do it all by hand - Dynamic Forms is very easier for this type of task. I created the quick search form on the company and product lists with OWS, though. When submitted, it runs a stored procedure which brings the appropriate results - illegal character escaping, injection protection, even parameter type checking and default values assignment are all built-in and easily customizable.

What's the catch? Well, lack of documentation. You've got to turn a lot of knobs and push a lot of buttons to understand what OWS really can do. Fortunately, there's a great community of OWS fans (me included), and this has solved most of my problems.

 

OWS Pros:

  • Free!
  • Very few bugs compared to other solutions
  • Integrated RAD development environment including debugger
  • It can talk to any external database (you can define connection strings)
  • Supports calls to webservices or even external DLLs
  • Supports caching, ajax, paging, sorting, decisions, variables
  • Can talk to DNN - actually it can retrieve everything the PortalSettings class offers and some more.
  • Very portable configurations, provides an option to even create your own PA assemblies, meaning that you can create your own modules using OWS.

OWS Cons:

  • VERY poor wiki-style documentation (but lots of examples in the community forums)
  • Internal functions have a syntax that is somewhat hard to read (quotes inside quotes, brackets, curly brackets, parentheses, escaped quotes, you can usually find them all in one line)
  • Developer must be very proficient in SQL and HTML / Javascript in order to achieve good results (actually IMHO, this is not a bad thing)
  • Can be painful to debug in very complex scenarios

Company website: www.r2integrated.com

Module website: www.openwebstudio.com

Snowcovered: Click

 

Click here to read Part 1 (introduction and Dynamic Forms from Datasprings)

Read more...

What modules I used to develop www.alunet.com (Part 1 - introduction and Dynamic Forms from Datasprings)

Instead of writing another boring "top 10" list of modules "you must use before you die", I chose to present a site I developed (currently online but in Beta stage). This is a unique opportunity, since I usually don't get permission or don't have enough time to publish details on my work.

 

The site is www.alunet.com, a global directory related to aluminium companies and products, and has been developed using the following DNN modules:

 

alunetcom_main

 

Dynamic Forms from Datasprings

IndooGrid from IndooLab

Open Web Studio from R2integrated

News Articles from Ventrian

Live Tabs from Mandeeps

Navigation Suite from DNN360.net

SmokeRanch Ad Manager from Smoke Ranch Software

 

(Please be aware that the design is not mine - the layout and site structure was entirely designed by the client and my job was to implement all the functionality needed).

 

The site is also heavily using jQuery, Fancybox (a jQuery plugin for displaying images and/or html) as well as ImageGen, an image resizer originally developed for the Umbraco CMS that can function stand-alone in any .NET - based website.

 

Let's discuss where I used what and why:

 

Dynamic Forms from Datasprings

 

Dynamic Forms is being used for almost all end-user data submission tasks. This is a very powerful forms generator for DotNetNuke, which allows you to create data-driven forms, storing data either in the module's internal database schema or in any custom external table structure. I am using custom tables for companies, products and categories so I chose the second approach. You can easily load data to your forms as well as save data to your database via SQL queries or stored procedures. You can populate form fields using SQL, and this is especially useful with lookup data used in combo boxes or checkbox groups.  The module supports various goodies like injecting your own javascript, creating your own validations and launching events on form submission that can send email or write to the database. I used Dynamic Forms for the product and company data submission forms that are displayed to registered users.

 

alunetcom_companyform

 

 

Registered users can submit their company data. Each user can submit only one company, which is approved by the site's administrator. Companies must have a main activity and subactivities that fall under their main activity (it's a tree with 3 levels). After initial submission, the user must select a main activity and subactivities. (again, Dynamic Forms is used for the selection forms).

 

When this process is complete, the company must be approved by the site administrators. Users get an email when this is done. (Of course, administrators also get an email for each submitted company). This is achieved via Dynamic Forms post-submission events.

 

Afterwards, the user is able to submit up to a specific number of products for the company (currently 10, but it's as easy to change as a record in a database table) and assign each product to one of the subactivities they have defined for their company. Actually, the "main activity" acts only as a grouping category, while subactivities are the actual activities of the company.

 

alunetcom_productform

 

When editing the subactivity list, the forms present only subactivities that can be selected depending on the main activity chosen and do not let the user unselect a subactivity he has already assigned products to.

 

 

alunetcom_activities

 

Users have their own "control panel" where they can manage companies, products and activities. This works mainly with Open Web Studio, which we'll examine later on.

 

Dynamic Forms pros:

  • Very powerful
  • Datastore in own schema or custom database
  • Post-submission events (email, sql, etc.)
  • Field events (when you fill a field - you can hide/show sections depending on value etc.)
  • Great selection of controls - including data-driven comboboxes, listbox/radio groups, rich text editor, image and file upload controls.

Dynamic Forms cons:

  • Bugs arise when you try to push it too far
  • Somewhat expensive - you must make sure that you need it
  • Not very good / easy control on final layout
  • Image and file upload controls have many features (e.g. unique filenames, automatic thumbnail resizing) but need a lot of work to operate correctly.

Company website: www.datasprings.com

DotNetNuke Store: Click

 

Click here to read Part 2 (Open Web Studio)

Read more...

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...
Related Posts with Thumbnails

Recent Comments

Free DotNetNuke Stuff

Free DotNet Videos

  © Blogger template The Professional Template by Ourblogtemplates.com 2008

Back to TOP