?

Log in

No account? Create an account
You know the body text of an article is too long to display in an overview listing. You want to provide a shorter excerpt, so the visitor will still get the general idea.

Can you simply change the SQL-instruction to retrieve the first say 256 characters of the field? Sure. However... that field contains HTML tags. What tags are still opened when you cut off the text at 256? Maybe you even cut the text right down the middle of a tag? While this method may prove fine for some articles, others can seriously break your mark-up, often resulting in seriously malformed pages. So no, can't simply do an SQL-based cut-off.

The next option is to let Smartsite remove the HTML tags for us. A simple xlinks macro can retrieve the text. Then we can have Smartsite remove the HTML tags using the plaintext encoding like so:
format="%plaintext:body%"


Then we need a cut-off. Hmm... can't use a custom translation in an xlinks format. Can use a vbscript viper, but that doesn't work. Tried postponing macro execution, also without success.

OK, different method. Let's still have Smartsite remove the HTML tags for us, and perform the cut-off, but this time we write the whole thing in VBScript. And while we're at it, we throw in a routine to help us cutting off the text in-between words or HTML entities. Lo and behold, it works.

<se type="script" language="vbscript" error="{error}" header="<ul>" footer="</ul>">

Dim strSQL, rst, strShorter, arrWords, i, intCutOff
strSQL = "select nr, body from vwActive where parent = 1"
Set rst = AdoHelper.Execute(strSQL)
strShorter = ""
intCutOff = 256

Do While Not rst.EOF

strShorter = Left(RemoveAllTags(rst("body")),intCutOff)
arrWords = Split(strShorter, " ")
strShorter = ""
i = 0

Do While Len(strShorter) < intCutOff And i < UBound(arrWords)

strShorter = strShorter & " " & arrWords(i)
i = i + 1

Loop

Context.Response.Buffer.AddLine("<li>" & strShorter & "...</li>")
rst.MoveNext

Loop

Set rst = Nothing
</se>


Plenty left to improve:

  • Vary how much text should be returned

  • Vary the query to retrieve data

  • Add a hyperlink to the Smartsite item as well

  • etc



Needless to say, this kind of thing is much easier in Smartsite iXperion...

Happy coding!
 
 
Current Mood: creativecreative
 
 
Sometimes, a Smartsite Duallist needs replacing with something users like better. In those cases, a Checkbox List comes in handy. Smartsite offers its own Checkbox List component, but what if we wish to use it in our own forms? We build our own.

Check box list example

There's 2 problems to solve:
1. How do we combine the data for the previously chosen options with the data for the not yet chosen options into 1 list, and how do we tell the HTML form which options are chosen?
2. How do we make the list look nice when the option texts run long enough to wrap to the next line?


1. Getting the data


We know that we can tell a checkbox input it's chosen by setting its "checked" attribute. The fastest way to set this, is by having the database query return the correct setting. Luckily, SQL queries can return literal strings. So all we need are two batches of data: one batch of the chosen options, and one batch of the remaining options. Then we combine them into one, using a UNION statement, which we execute using an se:sqlquery macro.

Like so:

select Nr, Description, 'checked="checked" ' as strChecked from tblPossibleOptions
inner join (select NrOption from tblUserChosenOptions where NrUser=?:userid2) as tblUserOptions
on tblPossibleOptions.Nr = tblUserOptions.NrOption

UNION

select Nr, Description, ' ' as strChecked from tblPossibleOptions
left join (select NrOption from tblUserChosenOptions where NrUser=?:userid1) as tblUserOptions
on tblPossibleOptions.Nr = tblUserOptions.NrOption
where tblUserOptions.NrOption is null

order by Description Asc


See how we recognised that options weren't chosen: we created a left join between the possible options and the ones chosen by the user, and explicitely excluded those combinations that were chosen. This is the fastest way known to us today.

Also see how we generated a literal string named 'strChecked', containing the setting we need for our checkbox input.

Did you see the ORDER-BY clause? This sorts the rows in alphabetical order of the option's description, instead of the order in which the data was retrieved.

Also see that we employ a new feature in Smartsite iXperion: we can identify the query parameters by appending a colon and an identifyer to the question mark, like so: "?:identifyer". Those identifyers have to be unique, which is why we chose to number them. Be sure to put the same user identification into each parameter!

The rowformat for the se:sqlquery macro will take this data and create a format per data row. In our case, we wish to enter the literal strChecked from our data into the format. To get iXperion to allow mixing strings and attributes, we need to encase a bit of the format in CDATA blocks, like so:


<![CDATA[<label
for="chkUserOption_]]>{this.field(nr)}<![CDATA["
><input type="checkbox" name="chkUserOption"
id="chkUserOption_]]>{this.field(nr)}<![CDATA["]]>
{this.field(strChecked)}
<![CDATA[value="]]>{this.field(nr)}<![CDATA["
/>]]>{this.field(Description)}<![CDATA[</label>]]>


(Think that looks jumbled? Try writing it in HTML with entities for < and >!)


Making the long wrapped lines look nice


We envelope the checkbox and its description in a single label element. That way, when someone clicks the description, the checkbox will be checked or unchecked. This proves easy to use.

However, you will recognise that some option descriptions run so wide, that they wrap lines, and the description text will end up immediately below the checkbox. That doesn't look very nice. It'd be nicer if all next lines for the same description start at the same indentation as the first line.

And no, we did not choose to employ a table for this. Instead, we used a bit of CSS. We indent the text a bit, and then outdent the first line. Like so:


.checkboxlist label { padding-left: 2em; text-indent: -2em; }


Seems easy, yes? Everything seems easy once you know how.

Happy coding!
 
 
Current Mood: nerdynerdy
 
 

Symptom

While uploading a 10MB file, Smartsite iXperion will generate an error, telling you "Maximum request length exceeded".

Solution


Remember this article? http://smartsite.livejournal.com/11571.html

It outlined the actions to take in order to allow Smartsite 5.2 to save large articles and upload large files. In Smartsite iXperion, things are a leetle bit more difficult. Yes, the IIS AspMaxRequestEntityAllowed property still needs to be set. But there's more.

Also set the registry entry Upload/MaxSize to a sufficient length (its value is measured in kilo bytes).

Also set the registry entry Filesystem/FileNameMaxLength to a sufficient length (its value is meeasured in characters), to allow for long file names and deep folder structures.

Also set the web.config setting httpRuntime like so:
<system.web>
<httpRuntime executionTimeout="5024" maxRequestLength="1048000" />
...
</system.web>


Know that changing the web.config will reset the IIS. Warn your customers and support crew.

Happy configuring!
 
 
Current Mood: annoyedannoyed
 
 
28 April 2009 @ 07:13 pm
Alright, this will be a longer article. Sorry 'bout that. Took us long enough to figure it all out, too.

Wish: a menu for your web site, that reflects the CMS hierarchy.


Problem: Your CMS is Smartsite 5.1c - 5.3.


Reflecting CMS hierarchy proves difficult, without resorting to page components built in MS Visual Basic.

Possible Solution: recursing XLinks


Create a translation holding an XLinks macro, which has a format that calls back to itself.

Using an XLinks macro, we use Smartsite's built-in ability to follow the CMS hierarchy. No other macro has this ability, not even the Sitemap macro. Having the macro call back to itself makes the output recurse down the hierarchy.

Like the incorrect example below, in case we name the translation 'xlinkRecurse':

<se type="xlinks" parent="{translationParam(1)}" format="<li>[xlinkRecurse(%nr%)]</li>" header="<ul>" footer="</ul>" />

(This example code will not work. The problem is described below.) The parameter %nr% is filled in by the XLinks macro itself. By passing it to the xlinkRecurse translation, the translation can catch it as param(1) and pass it back into the XLinks macro, as the value for the parent attribute, completing the circle.

Problem: translations and other macros called from macro formats are rendered too early


Generally a translation in a macro format will be rendered before the macro generated its output, running only once. We want the translation to render afterwards, running for each iteration of the macro output.

Solution: hold the execution


Quite literally, we add the text "hold-" to the name of the translation. This means that instead of rendering the actual translation, the XLinks macro will render an unresolvable text. Afterwards, a Replace macro will remove the "hold-", causing Smartsite to parse the output again, this time recognising and rendering the translation.

Like the working example code below:

<se type="replace" search="hold-" replace="">
<se type="xlinks" parent="{translationParam(1)}" format="<li>[hold-xlinkRecurse(%nr%)]</li>" header="<ul>" footer="</ul>" />
</se>


Wish: show which menu item is a folder


The XLinks macro uses a database query that includes the database field "folder". This field returns either 0 (is not a folder) or 1 (is a folder). We can use that in our format attribute, as part of a style class, like so:

<se type="replace" search="hold-" replace="">
<se type="xlinks" parent="{translationParam(1)}" format='<li class="folderstyle_%folder%">[hold-xlinkRecurse(%nr%)]</li>' header="<ul>" footer="</ul>" />
</se>


Then the generated HTML will contain classes "folderstyle_0" for an item that isn't a folder, and "folderstyle_1" for an item that is. A bit of CSS will do the rest.

Wish: do not recurse on empty folders


A folder in the Smartsite CMS may prove empty, instead of containing other items. Even when it does, those items might have to remain hidden from the menu. In those cases, we do not want our recursive XLinks macro to continue recursing... it would not only enter empty elements into our HTML, which then invalidates our mark-up, but it would also recurse too much, making our site slow.

Possible Solution: let the VBScript Viper run a query


We can employ a query like this:

select count(*) from [channelview] where parent=cast(? as int) and hiddenchild=0


If the query returns 0, we can stop recursing. Using a VBScript Viper in a criteria attribute will allow us to compare the query result with 0, like so:

<se criteria='{vbscript:ado.executescalar("select count(*) from [channelview] where parent=cast(? as int) and hiddenchild=0","{translationParam(1)}")} > 0'>
<se type="replace" search="hold-" replace="">
<se type="xlinks" parent="{translationParam(1)}" format='<li class="folderstyle_%folder%">[hold-xlinkRecurse(%nr%)]</li>' header="<ul>" footer="</ul>" />
</se>
</se>



Wish: show empty folders as not a folder


A folder in the Smartsite CMS may prove empty, instead of containing other items. Even when it does, those items might have to remain hidden from the menu. In those cases, we do not want the item to show up as a folder... otherwise the visitors get confused.

Solution: add a query to the XLinks macro


We can employ a query like this:

select folder = case when (select count(*) from [channelview] a2 where a2.parent = a.nr and a2.inmenu = 1 and a2.hiddenchild = 0) > 0 then 1 else 0 end
from [channelview] a where a.nr = cast(? as int)

(for MS SQL Server)

or like this:

select decode((select count(*) from [channelview] a2 where a2.parent = a.nr and a2.inmenu = 1 and a2.hiddenchild = 0),0,0,1) as folder
from [channelview] a where a.nr = cast(? as int)

(for Oracle)

Now notice that we didn't actually ask whether the CMS thinks the item is a folder... we merely ask whether it has any children we can show.

Bonus Problem: no more hierarchy


Adding this query to the XLinks macro might seem trivial... however, by doing so we are met with the hierarchy problem: we chose to employ the XLinks macro because it automatically reflects the CMS hierarchy. Tinkering with its query usually means we cause the macro to drop this ability.

Bonus Solution: adding the hierarchy back into the macro


The way a folder is sorted in the CMS, is stored as a string in the database record of that folder. Unfortunately, neither SQL Server nor Oracle are able to take that data and employ it as a sort order in one and the same query. So how can we retrieve that sort order and employ it, too?

By using yet another VBScript Viper. Like so:

{vbscript:ado.executescalar("select isnull(c.listorder,'sortindex') from contents c where c.nr=cast(? as int)","{translationParam(1)}")}

(for MS SQLServer)

or

{vbscript:ado.executescalar("select nvl(c.listorder,'sortindex') from contents c where c.nr=cast(? as int)","{translationParam(1)}")}

(for Oracle)

Put together we get a query much like the following:

select a.nr, a.parent, a.title, a.hiddenchild,
folder = case when (select count(*) from [channelview] a2 where a2.parent = a.nr and a2.inmenu = 1 and a2.hiddenchild = 0) > 0 then 1 else 0 end
from [channelview] a where a.parent = cast(? as int) and a.hiddenchild = 0
order by a.{vbscript:ado.executescalar("select isnull(c.listorder,'sortindex') from contents c where c.nr=cast(? as int)","{translationParam(1)}")}

(for MS SQLServer... you should be able to puzzle it together for Oracle by now.)

Finale


The end result of these wishes culminates into a translation that reads like this:


<se criteria='{vbscript:ado.executescalar("select count(*) from [channelview] where parent=cast(? as int) and hiddenchild=0","{translationParam(1)}")} > 0'>
<se type="replace" search="hold-" replace="">
<se type="xlinks" parent="{translationParam(1)}" format='<li class="folderstyle_%folder%">[hold-xlinkRecurse(%nr%)]</li>' header="<ul>" footer="</ul>" sqlparams="long:{translationParam(1)}" sql="*"><![CDATA[
select a.nr, a.parent, a.title, a.hiddenchild,
folder = case when (select count(*) from [channelview] a2 where a2.parent = a.nr and a2.inmenu = 1 and a2.hiddenchild = 0) > 0 then 1 else 0 end
from [channelview] a where a.parent = cast(? as int) and a.hiddenchild = 0
order by a.{vbscript:ado.executescalar("select isnull(c.listorder,'sortindex') from contents c where c.nr=cast(? as int)","{translationParam(1)}")}
]]></se></se></se>

(for MS SQLServer)

Happy coding!
 
 
Current Mood: accomplishedaccomplished
 
 
21 February 2009 @ 11:50 pm
You may have noticed that iXperion allows 3 different writing methods for its macro programming.

Some examples:

1.

<se:sqlquery save="mydata" sql="select fullname from visitors where nr = ?" params="{user.userid()}" />

2.

<se:sqlquery save="mydata">
<se:sql>select fullname from visitors where nr = ?</se:sql>
<se:params>{user.userid()}</se:params>
</se:sqlquery>


3.

<se:sqlquery save="mydata"><se:parameters>
<se:parameter name="sql"><![CDATA
select fullname from visitors where nr = ?
]]></se:parameter>
<se:parameter name="params"><se:collection>
<se:member>{user.userid()}</se:member>
</se:collection></se:parameter>
</se:parameters></se:sqlquery>


What did you notice?
 
 
Current Mood: geekygeeky
 
 
 
21 February 2009 @ 11:30 pm
Up to Smartsite 5.3, typing in upper or lower case didn't matter: the code would run.

That's changed. iXperion is cAsE sEnSiTiVe.

- Comparison operators in expressions: UPPERCASE.
- Vipers and translation names: lowercase.

Check your manual.

Now all it needs is extend its own case sensitivity to its handling of data, especially when using case sensitive databases like Oracle.
 
 
Current Mood: contemplativecontemplative
 
 
21 February 2009 @ 11:45 pm
Analogy:

You give your child a box of legos. Them all the same, but for the colours. There's 6 different colours of lego blocks.

Then you build a little wall of legos. You tell your kid: you can extend this wall (put in more blocks), or you can take some out and put in some others. However, you built that wall in such a way, that it can only have red, green, and blue. Any other colour, and the wall will collapse.

And you don't tell your kid.

Wait for it...

Wall fall down, go boom.

That's why you should avoid reusing fields that are used already by Smartsite's own content types. When you add your own fields, it doesn't matter when your kid adds a block of a 4th or 5th colour, and your wall will stay upright.

So add your own fields.
 
 
Current Mood: annoyedannoyed
 
 
28 April 2009 @ 04:35 pm

Changing passwords



When building a method to allow authenticated visitors to change their log-in credentials, we recognise iXperion 1.0 helps us by supplying the {user.changepassword()} viper method. It accepts 3 parameters:

1. The visitor's identification (read from {user.userid()})
2. The old password (input via form)
3. The new password (input via form)

Now realise that Smartsite iXperion automatically xml-encodes the values received from form inputs. Realise also that the {user.changepassword()} method does not decode passed arguments. Concluding: we have to do the decoding ourselves, employing {string.xmldecode()} before passing the passwords to the method.

Realise that the passwords can contain almost any character, as they are stored encrypted. This will only become problematic when reading and decrypting the passwords... which we shouldn't. At all.

After a succesful password change, the visitor remains logged in.


Changing log-in names



When we want to allow the authenticated visitor to change their log-in name, we recognise that iXperion 1.0 does not help us. We have to write an SQL query.

After changing the visitor's log-in name, the web page will return a .Net error message, complaining that the old visitor log-in can't be found. We solve this by forcing the visitor to log out immediately after changing their log-in name.

Since the log-out method {login.undo()} automatically refreshes the web page, the visitor will be forced to log in anew. If the visitor disables the automated web page refresh (Opera users can do that, for instance), they are met with the giant .Net error message, and further access will be denied.

Because of the auto log-out and refresh, it is wise to check wether the log-in name was changed at all, rather than simply accepting a form post and forcing the visitor to log in again.


Putting it together



Reading both parts above, we have to conclude that though it may seem sensible to have a visitor change their log-in name and password in one go, the programming code behind the scenes must handle the changes separately: first the passwords, then the log-in name.

Happy coding!
 
 
Current Mood: accomplishedaccomplished
 
 
04 October 2008 @ 12:53 pm
Setting the value of the CTFP (contenttype field property) 'Default' to 'parent', we are able to tell the Smartsite CMS 5.2 to inherit information for new items from their hierarchical parent. As long as the new item and the parent item belong to the same contenttype group, all values should be inherited without problem.

However, as soon as they belong to different groups, the fields of which the values are not stored in the Contents table, but in related tables using for instance ForeignKeys, ContentRelations and ContentsThesTerms, no longer support inheritance.

Can we solve this?

Yes, we can!

It takes a small bit of script, like the one shown at the bottom of Smartsite's official Support site for the Default CTFP... only instead of filling the returnValue with a string of our own, we fill it with the result of Site.CMS.getFieldValue().

It can be that simple.
 
 
Current Mood: chipperchipper
 
 

Update

With hotfix 10 for Smartsite 5.2, this issue seems to be resolved. Contact your Value Added Reseller.

Yes, you've read that right: a bug in the Export Module for Smartsite DWS 5.2 and newer may cause the web server to be deleted.

Technical details:

The Smartsite Export Module is used to export Smartsite Content Items as well as Content Types, for ease of transfer to other environments. The module sports an option to specify a target folder for the items to be exported, and an option to empty that target folder before exporting anything. In some cases, Smartsite does not recognise the target folder, and will empty out and export items to the folder where the web server is located. Bye bye, web server.

File locations and symptoms:

For Microsoft Internet Information Server 6 on Windows 2000 and Windows 2003, this folder is %windir%\system32\inetsrv\. For Smartsite's own web server, it's %Program Files%\SmartsiteDWS\Smartserver5\. The problem can be detected by the failure of the web server to respond. In case of using MSIIS, the IIS Admin Service will fail, reporting it can't find the file specified.

Work-around:

Don't let the Smartsite Export Module empty out the target folder, and always specify a pre-existing folder, created by a 3rd-party-application like MS Windows Explorer.

Solution:

The manufacturor is aware of this problem and is working on a fix as we speak. Contact your Value Added Reseller.
 
 
Current Mood: annoyedannoyed