AngularJS directive for windows.history.back()

In case you want to implement windows.history.back() in a pure AngularJS way, this is how you do it.

First, create a simple directive:

angular.module("backButton", [])
.directive("backButton", ["$window", function ($window) {
    return {
        restrict: "A",
        link: function (scope, elem, attrs) {
            elem.bind("click", function () {
                $window.history.back();
            });
        }
    };
}]);

Then, in your HTML, you add the directive to an element – like so:

<a ng-href="#" back-button class="link-back">
<span class="icon icon-arrow-left"></span>
</a>

And that’s it.

AngularJS directive for adding focus to an input box based on some other value

I needed a directive to enable focus in an input text element when some variable was set to true. The case was that the user would open up an accordion and the input element would appear. The mouse had to be placed inside the input element on show.

I found the following blog-article describing how to do this. It works just like a charm: http://www.angulartutorial.net/2014/04/angular-js-auto-focus-for-input-box.html

Dynamicweb Software

Code-first items in Dynamicweb CMS

Items in Dynamicweb CMS is a wonderful thing. It allows you to create your own specialized content types and it makes it easy to create custom functionality. Normally, I have been used to creating simple items via the Item Manager in the administration area, and this works perfectly for simple projects where you e.g. need to enable the webmaster to create custom news or calendar elements – simple stuff.

However, in a new project we needed to use the code-first approach. In this project we have a lot of front end stuff going on, and therefore the regular ItemPublisher- and Itemcreator-module in Dynamicweb just didn’t cut it. We needed more than this, and therefore we decided to build our own publisher and creator functionality. In order to do that we needed to be able to create all items via .net.

Now, when you head out in such a project you don’t always see the obstacles right in front of you. One of the main obstacles is that the items-functionality in Dynamicweb CMS is quite new and it is evolving very quickly. If only the documentation was evolving just as quickly!

This is the reason why I’m documenting my experiences here in this blog. I’ve been spending quite a lot of time investigating different approaches – both via my colleagues but also via the Developer Forum. Luckily, the developers at Dynamicweb Software are very quick to provide answers for my questions. The Developer Forum has been my savior a lot of times(!).

The first blogpost here will sum up some of the learnings that I’ve made when creating items via code. This is primarily for my own sake, but I hope that others might be able to use this knowledge as well. Please notice that I will not describe the stuff that is already documented on the developer website right here: http://developer.dynamicweb.com/documentation/for-developers/item-based-structure/getting-started.aspx

Please be aware, that if you create items code-first and you need to modify items later on in the process, you will often need to delete the XML-file representing the item, and also the database table containing the item entries. Otherwise, you might risk that the changes doesn’t appear.

Creating my base item
I first needed to create a base item that other items would inherit from. This code looks like the following:

namespace Bleau.Custom.ItemSubscriber.Items
{
    using Dynamicweb.Content.Items;
    using Dynamicweb.Content.Items.Activation;
    using Dynamicweb.Content.Items.Annotations;

    [Item("Base", "Base"), Category("Custom")]
    [AreaRule,
    StructureRule(StructureContextType.Paragraphs),
    ParentRule(ParentRestrictionRule.ParentType.RootOfWebsite, ParentRestrictionRule.ParentType.RegularPage),
    TitlePattern("{{Title}}"),
    TitleField("Title")]
    [CustomizedURLs]
    public class Base : ItemEntry
    {
        [Name("Title")]
        [Required]
        public string Title { get; set; }

        [Field("Category", typeof(Dynamicweb.Content.Items.Editors.DropDownListEditor<>))]
        [OptionItem("Category", "CategoryName", "Id", IncludeChilds = true, IncludeParagraphs = true, ItemSystemName = "Category", NameField = "CategoryName", SourceId = 32, SourceType = Dynamicweb.Content.Items.Metadata.FieldOptionItemSourceType.CurrentArea, ValueField = "Id")] 
        [Required]
        public string Category { get; set; }

        //private DateTime? activeFrom;

        //[Field("Active From", typeof(Dynamicweb.Content.Items.Editors.DateTimeEditor))]
        //[Required]
        //[DefaultValue("Now")]
        //public DateTime Active_From { get; set; }

        //[Field("Active To", typeof(Dynamicweb.Content.Items.Editors.DateTimeEditor))]
        //[Required]
        //[DefaultValue("Now")]
        //public DateTime Active_To { get; set; }

        [Field("AccessUserID", typeof(Dynamicweb.Content.Items.Editors.HiddenFieldEditor))]
        public string AccessUserID { get; set; }

        [Field("Permission", typeof(Dynamicweb.Content.Items.Editors.HiddenFieldEditor))]
        public string Permission { get; set; }

        [Field("Allow Comments", typeof(Dynamicweb.Content.Items.Editors.CheckboxEditor))]
        [DefaultValue(false)]
        public bool AllowComments { get; set; }

        [Field("Tags", typeof(Dynamicweb.Content.Items.Editors.CheckboxListEditor<>))]
        [OptionItem("Tag", "TagName", "Id", IncludeChilds = true, IncludeParagraphs = true, ItemSystemName = "Tag", NameField = "TagName", SourceId = 82, SourceType = Dynamicweb.Content.Items.Metadata.FieldOptionItemSourceType.CurrentArea, ValueField = "Id")] 
        public string Tags { get; set; }
    }
}

There are a couple of things going on here:

Decorating your item/class with relevant attributes

    [Item("Base", "Base"), Category("Custom")]
    [AreaRule,
    StructureRule(StructureContextType.Paragraphs),
    ParentRule(ParentRestrictionRule.ParentType.RootOfWebsite, ParentRestrictionRule.ParentType.RegularPage),
    TitlePattern("{{Title}}"),
    TitleField("Title")]
    [CustomizedURLs]

The code repeated here adds the necessary attributes to your item. It will create a name, and put it into a category called “Custom”. It will enable the item for paragraphs only, and it will allow the item to be created in the root of the website and underneath a regular page. It will also instruct the item to use the field called “Title” as the name of the paragraph, and finally it will create customized URLs.

Instruct a DropDownListEditor to use data from another item

        [Field("Category", typeof(Dynamicweb.Content.Items.Editors.DropDownListEditor<>))]
        [OptionItem("Category", "CategoryName", "Id", IncludeChilds = true, IncludeParagraphs = true, ItemSystemName = "Category", NameField = "CategoryName", SourceId = 32, SourceType = Dynamicweb.Content.Items.Metadata.FieldOptionItemSourceType.CurrentArea, ValueField = "Id")] 
        [Required]
        public string Category { get; set; }

Another thing that is not documented is the ability to populate a DropDownListEditor with data from another item. I had already created an item called category, and the code above allows you to use this item in your dropdowns or checkboxes. You just add the “OptionItem”-attribute, and instruct it to use the “Category”-item. You can even tell the field to use the categories placed on a particular page which is why I’m using the SourceId = 32 attribute. I should probably put that one into a setting – arh well….

Date fields
As you can see I have chosen to comment out the date fields. I’m still having trouble with this one. The problem is that Dynamicweb doesn’t create a real date field in the database when you use code-first, and hence it will crash when you try to add new dates to the item. This is probably me doing some oddball-thing, but I still haven’t figured out how to do this properly. When I create this via the Item Manager everything works perfectly, but just not from my code. I hope to have this fixed as soon as possible.

Inherit from another item
It is possible to let one item inherit fields from another item. In my case, I needed to create an article-item that would inherit from the base-item. This is done via regular C#-code like the following:

namespace Bleau.Custom.ItemSubscriber.Items
{
    using Dynamicweb.Content.Items.Activation;
    using Dynamicweb.Content.Items.Annotations;

    [Item("Article", "Article"), Category("Custom")]
    [AreaRule,
    StructureRule(StructureContextType.Paragraphs),
    ParentRule(ParentRestrictionRule.ParentType.RootOfWebsite, ParentRestrictionRule.ParentType.RegularPage),
    TitlePattern("{{Title}}"),
    TitleField("Title")]
    [CustomizedURLs]
    public class Article : Base
    {
        [Name("Teaser")]
        public string Teaser { get; set; }

        [Field("Text", typeof(Dynamicweb.Content.Items.Editors.LongTextEditor))]
        public string Text { get; set; }

        [Field("Files", typeof(Dynamicweb.Content.Items.Editors.LongTextEditor))]
        public string Files { get; set; }
    }
}

As you can see, you just inherit from the Base-class and this will be represented in Dynamicweb CMS automatically.

So this is it! I will update this blogpost when I get new information about the date fields. Hopefully, it is just me messing around….

AngularJS logo

Create an AngularJS datefilter to properly display JSON date strings

Working with AngularJS often puts me in a situation where I need to deal with date strings in JSON returned from some .net server and looking like the following:

/Date(1297246301973)/

The string returned here represents the date in milliseconds and before you can use this date in your application, you need to convert it properly. Luckily, in AngularJS you can just create a custom filter like the following:

app.filter("dateFilter", function () {
    return function (item) {
        if (item != null) {
            return new Date(parseInt(item.substr(6)));
        }
        return "";
    };
});

You can then use the filter in the following way in your HTML:

    <tr ng-repeat="t in Result">
        <td>{{t.Date | dateFilter | date:"dd-MM-yyyy"}}</td>
    </tr>

You can see here that first I convert the JSON date string to a real date, and then I just use the regular date-filter in AngularJS in order to properly format it.

AngularJS logo

Creating a custom AngularJS filter to display items between values

Sometimes you need to filter out certain items in a list based on a numeric value. The following code accomplish that.

Put this into your controller:

    // Comparison filter
    $scope.inBetween = function (property, lowbound, highbound) {
        return function (item) {
            if (item[property] >= lowbound && item[property] <= highbound) return true;
            return false;
        }
    }

And then you just use the filter in the following way in your ng-repeat:

<tr ng-repeat="a in Result | filter: inBetween('accountnumber', 10100000, 15000000)">
</tr>
AngularJS logo

Filtering AngularJS items based on start and end date

There are cases where you would like to filter existing items in an AngularJS ng-repeat and only display the items where the date of the item is between any given start and end date. The following code accomplish that – please notice that I have only tested this with AngularJS version 1.2.22 and also notice that I use moment.js for the parsing of the date.

The following code is the filter – put this into a controller:

    // Daterange filter
    $scope.dateRangeFilter = function (property, startDate, endDate) {
        return function (item) {
            if (item[property] === null) return false;

            var itemDate = moment(item[property]);
            var s = moment(startDate, "DD-MM-YYYY");
            var e = moment(endDate, "DD-MM-YYYY");

            if (itemDate >= s && itemDate <= e) return true;
            return false;
        }
    }

And then – in your ng-repeat – you insert the following code:

    <tr ng-repeat="t in Result | filter: dateRangeFilter('itemDate', startDate, endDate)">
        <td>{{t.Name}}</td>
        <td>{{t.Adresse}}</td>
        <td>{{t.Phone}}</td>
        <td><a ng-href="mailto:{{t.Email1}}">{{t.Email1}}</a></td>
        <td>{{t.itemDate | dateFilter | date:"dd-MM-yyyy"}}</td>
    </tr>
AngularJS logo

Detecting route changes in AngularJS

Working with AngularJS there are cases where you want to execute code in a controller when the user clicks a link and the route changes.

In the following I’m triggering a new search based on querystring parameters.

function TenantsCtrl($scope, $http, $routeParams, $location, fetchFinancialService) {
    $scope.$on('$routeChangeSuccess', function () {
        if (typeof $routeParams.startDate === "undefined" || typeof $routeParams.endDate === "undefined") {
            $scope.startDate = "01-01-2000";
            $scope.endDate = "31-12-" + new Date().getFullYear();
        } else {
            $scope.startDate = $routeParams.startDate;
            $scope.endDate = $routeParams.endDate;
        }
    });

    fetchFinancialService.fetchTenants($scope, $http, $routeParams);
}

Please notice that it is the $routeChangeSuccess-event that I utilize for this purpose.

Disqus

Properly resizing Disqus thread in an iframe

I had a use case where I needed to insert Disqus-threads into an iframe. Which should’ve been pretty easy.

The problem, however, is that the height of the Disqus-thread is dynamic which means that you cannot set the height beforehand. You need to set the height of the iframe when everything in the Disqus-thread is loaded. After a lot of searching I figured out the following solution for the problem.

Disqus pushes the events called “onReady” and “onNewComment” and you can use these events in the following way to get the height:

    <script type="text/javascript">
        /* * * CONFIGURATION VARIABLES: EDIT BEFORE PASTING INTO YOUR WEBPAGE * * */
        var disqus_shortname = 'SHORTNAME'; // required: replace example with your forum shortname

        /* * * DON'T EDIT BELOW THIS LINE * * */
        (function () {
            var dsq = document.createElement('script'); dsq.type = 'text/javascript'; dsq.async = true;
            dsq.src = '//' + disqus_shortname + '.disqus.com/embed.js';
            (document.getElementsByTagName('head')[0] || document.getElementsByTagName('body')[0]).appendChild(dsq);
        })();

        function disqus_config() {
            this.callbacks.onReady = [function () {
                var newsId = getParameterByName('newsId');
                setTimeout(function() {
                    var frameHeight = $(document).height();
                    $('#disqus_iframe_' + newsId, top.document).attr("height", frameHeight);
                }, 2000);
            }];
            this.callbacks.onNewComment = [function () {
                var newsId = getParameterByName('newsId');
                var frameHeight = $(document).height();
                $('#disqus_iframe_' + newsId, top.document).attr("height", frameHeight);
            }];
        }

        function getParameterByName(name) {
            name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
            var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
                results = regex.exec(location.search);
            return results == null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
        }
    </script>

In the code above I execute code onReady and onNewComment. I grab the height of the document and then set the iframe height accordingly. Please notice, that it is not enough to just listen to the onReady-event as the complete list of comments is not loaded when this event is fired. Instead, you will have to set up a timer function and wait a couple of seconds in order to let all comments load properly – and then you can get the proper height. Not an elegant solution, but it works.

Remember to check “Include paragraph items” when upgrading from Dynamicweb 8.2 to 8.4

When updating a Dynamicweb solution, you need to keep an eye on all the important notes provided by Dynamicweb Software. However, I encountered this undocumented feature today when I was updating a Dynamicweb-solution from version 8.2.1 to 8.4.10. All of a sudden all items disappeared.

I investigated the release notes, and made sure to verify all item settings in management settings – to no avail. The solution was to check the “Include paragraph items” on each paragraph containing the itempublisher-module (sigh!).

Just like this:

Include_paragraph_items

Styling a group of checkboxes as a dropdown via CSS and JavaScript

The following demonstrates how to create a dropdown list based on a list of checkbox elements. You can view a complete example of how this works at the following fiddle: http://jsfiddle.net/Terkildsen/mTSLa/

In the following I will walk you through the code.

First, I start by creating the HTML:

    <div class="dropdown">
        Choose city
        <ul class="dropdown-list">
            <li>
                <label>
                    <input type="checkbox" value="Vejle" name="city" />Vejle</label></li>
            <li>
                <label>
                    <input type="checkbox" value="Horsens" name="city" />Horsens</label></li>
            <li>
                <label>
                    <input type="checkbox" value="Kolding" name="city" />Kolding</label></li>
            <li>
                <label>
                    <input type="checkbox" value="Fredericia" name="city" />Fredericia</label></li>
        </ul>
    </div>

There’s not much to say about the HTML above. I use a “dropdown” div to contain the entire dropdown – including the default text that is displayed when the checkbox list is hidden. I then assign a “dropdown-list” class to the list of checkboxes.

Then, I start styling the dropdown:

.dropdown {
    width: 200px;
    border: 1px solid silver;
    cursor: pointer; /* use correct mouse pointer when hovering over the dropdown */
    padding: 10px;
    position: relative;
    margin: 0 auto;
    -webkit-touch-callout: none;
    -webkit-user-select: none;
    -khtml-user-select: none;
    -moz-user-select: none;
    -ms-user-select: none;
    user-select: none;
}

In the CSS above I start by styling the entire dropdown. I provide the dropdown with a width of 200px, some padding and a border to make the dropdown resemble a normal select-list.

Notice that the usage of the “user-select: none;” prevents the user from accidentally highlighting the text in the dropdown.

/* Display CSS arrow to the right of the dropdown text */
.dropdown:after {
    content:'';
    height: 0;
    position: absolute;
    width: 0;
    border: 6px solid transparent;
    border-top-color: #000;
    top: 50%;
    right: 10px;
    margin-top: -3px;
}

Instead of inserting an image to display an arrow in order to indicate that this dropdown can be clicked on, I use a small CSS-trick that will display an arrow using a border-property. You can read more about how this is done in the following blogpost: http://pterkildsen.com/2012/09/16/creating-arrows-using-css/

/* Reverse the CSS arrow when the dropdown is active */
.dropdown.is-active:after {
    border-bottom-color: #000;
    border-top-color: #fff;
    margin-top: -9px;
}

When the user activates the dropdown, the CSS above reverses the direction of the CSS-arrow. It does this by applying the white background-color to the “border-top-color”-property so that this part of the border is hidden, and then displays the bottom part of the border by assigning a black color to the “border-bottom-color”-property.

.dropdown-list {
    list-style: none;
    margin: 0;
    padding: 0;
    position: absolute;
    top: 100%; /* align the dropdown right below the dropdown text */
    border: inherit;
    border-top: none;
    left: -1px; /* align the dropdown to the left */
    right: -1px; /* align the dropdown to the right */
    opacity: 0; /* hide the dropdown */
    -webkit-transition: opacity 0.4s ease-in-out;
    -moz-transition: opacity 0.4s ease-in-out;
    -o-transition: opacity 0.4s ease-in-out;
    -ms-transition: opacity 0.4s ease-in-out;
    transition: opacity 0.4s ease-in-out;
    pointer-events: none; /* avoid mouse click events inside the dropdown */
}

I then style the list of checkboxes. There are a couple of important issues here. First of all, I use the “opacity”-property to hide and display the checkbox-list. And secondly, I use the “pointer-events: none” to prevent click events from happening in the checkbox-list – which would accidentally hide it.

.is-active .dropdown-list {
    opacity: 1; /* display the dropdown */
    pointer-events: auto; /* make sure that the user still can select checkboxes */
}

The is-active class is applied to the checkbox-list whenever the user clicks on the “dropdown-selector”. This class displays the checkbox-list by setting the “opacity”-property to 1 and it also makes sure that the user still can select the checkboxes by setting “pointer-events: auto;”

.dropdown-list li label {
    display: block;
    border-bottom: 1px solid silver;
    padding: 10px;
    -webkit-transition: all 0.2s ease-out;
    -moz-transition: all 0.2s ease-out;
    -o-transition: all 0.2s ease-out;
    -ms-transition: all 0.2s ease-out;
    transition: all 0.2s ease-out;
}

.dropdown-list li label:hover {
    background-color: #c41230;
    color: white;
}

And then finally, I just add some finishing touches to the labels inside the checkbox-list.

In order for this to work, I add a couple of lines of jQuery:

         $(function () {
            $(".dropdown").click(function () {
                $(this).toggleClass("is-active");
            });

            $(".dropdown ul").click(function (e) {
                e.stopPropagation();
            });
        });

The code above detects the click event on the “dropdown”-div – which will toggle the “is-active” class. It also captures the click-event on the list of checkboxes in order to make sure that this doesn’t accidentally close the dropdown.

This code has been tested in Internet Explorer 9+, the latest version of Mozilla Firefox and Google Chrome. I have also tested it on iPhone and iPad. Seems to be working just fine. However, please notice that I’ve had some issues with jsfiddle and Internet Explorer. The code works fine without jsfiddle, though.