Archive

Archive for the ‘Javascript’ Category

Differences in browsers engines under AngularJS

Working to updates on a AngularJS site, i discovered subtle differences.
I don’t know if there were these problems before, but certainly they raised while testing with Angular 1.6.5
First, if the user was switching the site language from English to Italian while working , saving a date Moment was not able to parse the date giving “Invalid date” using the latest Chrome and Firefox, while with Ie11 and Edge all ok.
In origin the code was (oh yeah, it uses the DOM objects instead of the binding..)

var testDate = moment($("#txtBirthDate").val()).format($scope.DateFormat.toUpperCase());
if (testDate === "Invalid date") {
    logWarning(localize.localizeText("DateNotValid"), null, true);
    $("#txtBirthDate").focus();
    return false;
}

For the first thing , using Visual Studio 2017 and starting the project i noticed that finally the breakpoints in javascript code are working also under Chrome, great thing:

Obvious , $scope.DateFormat.toUpperCase() is evaluated at runtime as YYYY-MM-DD for English and DD/MM/YYYY for Italian.
In order to work also with Chrome and Firefox this is the correct Moment syntax:

var testDate = moment($("#txtBirthDate").val(), $scope.DateFormat.toUpperCase()).format($scope.DateFormat.toUpperCase());

This code instead works without changes in all browsers:

var datBirthDate = moment($("#txtBirthDate").val(), $scope.DateFormat.toUpperCase()).toDate();
var BirthDate = datBirthDate.yyyymmdd();  // as requested for SQL Server

Then the other issue, an awkward issue.
The customers have the bad habit to press F5 or worse the browser refresh button in the pages, that in Angular does little sense.
In the site the other controllers (the ones not relatives to the home page) are loaded with ocLazyLoad (current version v1.0.10), in config.route.js the code was

}, {
    url: '/phones',
    config: {
        title: 'Phones',
        role: 'Administrator;PowerUser;User',
        templateUrl: 'app/tabelle/phones.html', 
        resolve: requireDependencies(['app/services/datacontextphones.js'], ['app/tabelle/phones.js']]),
        settings: {
            content: '<i class="fa fa-mobile"></i> {placeholder}',
            nav: 3,
            localizeKey: 'mnuPhones',
            minicontent: '<i class="fa fa-mobile" title="{placeholder}"></i>'
        }
    }
}, {

The function:

function requireDependencies(depContext, depMain) {
    return {
        resolvedData: ['$q', '$rootScope', '$ocLazyLoad', function ($q, $rootScope, $ocLazyLoad) {
            $ocLazyLoad.load(depContext);
            $ocLazyLoad.load(depMain);
        }]
    };
}

and this code with Ie11 and Edge was working fine, the users could hit F5, the browser refresh button, and the page is refreshed without problems.
Instead with Chrome and Firefox:

After some struggle, the working code in config.route.is that doesn’t use the function requireDependencies and all pages are surviving to the refresh in all browsers:

}, {
    url: '/phones',
    config: {
        title: 'Phones',
        role: 'Administrator;PowerUser;User',
        templateUrl: 'app/tabelle/phones.html', 
        resolve: {
            deps: ['$ocLazyLoad', function ($ocLazyLoad) {
                return $ocLazyLoad.load({
                    name: "phones",
                    files: [
                        'app/services/datacontextphones.js',
                        'app/tabelle/phones.js'
                    ]
                });
            }]
        },
        settings: {
            content: '<i class="fa fa-mobile"></i> {placeholder}',
            nav: 3,
            localizeKey: 'mnuPhones',
            minicontent: '<i class="fa fa-mobile" title="{placeholder}"></i>'
        }
    }
}, {

Advertisements
Categories: .NET, Angular, Javascript, Vs2017

Autocomplete extender with Id and Description

It was a very long time ago that i worked with the Ajax Toolkit, but sometimes you are requested to implement old projects..

And the language is VB.NET, that i was not using since 2003.

The request was to implement an Autocomplete extender from a customers list (from a Sql Server table), but in the database is requested to write the autoincrement ID, not the description.

In order to implement the ajax toolkit, in the .aspx markup must be added the header:

<%@ Register Assembly="AjaxControlToolkit" Namespace="AjaxControlToolkit" TagPrefix="cc1" %>

With the obvious adding of a reference , for the ASP.NET project, to AjaxControlToolkit.dll

A simple autoextender can be written as

<asp:TextBox ID="txtAgency" runat="server" ></asp:TextBox>
<cc1:AutoCompleteExtender ID="aceAgency" FirstRowSelected="false" MinimumPrefixLength="1" TargetControlID="txtAgency" ServiceMethod="GetListAgencies" runat="server">
</cc1:AutoCompleteExtender>

in the aspx page; in the code behind for this page we can implement a page method (the “ServiceMethod”) as

<System.Web.Script.Services.ScriptMethod(), _
System.Web.Services.WebMethod()> _
Public Shared Function GetListAgencies(ByVal prefixText As String, ByVal count As Integer) As List(Of String)
    Dim conn As SqlConnection = New SqlConnection
    '
    conn.ConnectionString = <string from web.config>
    Dim cmd As SqlCommand = New SqlCommand
    cmd.CommandText = "SELECT AnagId, AnagName FROM [dbo].[SomeTableOrView] WHERE AnagName LIKE @SearchText + '%'"
    cmd.Parameters.AddWithValue("@SearchText", prefixText)
    cmd.Connection = conn
    conn.Open()
    Dim lstAgencies As List(Of String) = New List(Of String)
    Dim sdr As SqlDataReader = cmd.ExecuteReader
    While sdr.Read
        lstAgencies.Add(sdr("AnagName").ToString))
    End While
    conn.Close()
    Return lstAgencies
End Function

This works, we type one letter (MinimumPrefixLength=”1″) on the keyboard and we are proposed with the list of names beginning with the typed letter.

But from the database in our sample we have only the AnagName value , the AnagId is not connected in some manner with the selected item: if we need to save that Id instead of the name this approach is not valid.

In order to save that id we need some javascript code and another approach in the web method.

First, this is the new aspx markup:

<asp:TextBox ID="txtAgency" runat="server"></asp:TextBox>
<cc1:AutoCompleteExtender ID="aceAgency" FirstRowSelected="false" TargetControlID="txtAgency" ServiceMethod="GetListAgencies" OnClientHidden="onClientHiddenAgency" MinimumPrefixLength="1"  EnableCaching="true" OnClientItemSelected="onClientItemSelectedAgency" OnClientPopulating="onClientPopulatingAgency" runat="server">
</cc1:AutoCompleteExtender>
<input type="hidden" id="hdnAgencyId" name="hdnAgencyId" runat="server" value="0" />

The OnClient… properties are the names of javascript functions; the EnableCaching is true because the underlying data are in this case very static.

Note that there is an hidden field where to save the requested Id and this is runat=server because we need to refer this field when we will save the record.

The javascript functions (ok, not the best javascript code…):

var boolChosenAgency = false;

function onClientItemSelectedAgency(source, e) {
    window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = e.get_value();
    boolChosenAgency = true;
}

function onClientHiddenAgency(source, e) {
    if (!boolChosenAgency) {
        window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = "0";
        window.document.getElementById("<%=txtAgency.ClientID%>").value = "";
    }
}

function onClientPopulatingAgency(source, e) {
    window.document.getElementById("<%=hdnAgencyId.ClientID%>").value = "0";
    boolChosenAgency = false;
}

What happens with this code is:

  • When the user begins to write, the Id value is changed to 0 and boolChosenAgency is set to false (“the user has not stille chosen an valid value”)
  • Only when an item is chosen the Id (coming from e.get_value()) is written in the hidden field
  • OnClientHiddenAgency is always executed, if the user has opened the list but has not clicked on a value then the id value is changed to 0

On the code behind so we must verify : if the hidden id is 0 and/or the textbox is empty the the id must be null on the db, otherwise we can write the int value.

The scope of the code is also: if the user write a not valid text, the Id is invalidated.

But how we can have the id in e.get_value() ?

This is done with this code for the web method:

<System.Web.Script.Services.ScriptMethod(), _
System.Web.Services.WebMethod()> _
Public Shared Function GetListAgencies(ByVal prefixText As String, ByVal count As Integer) As List(Of String)
    Dim conn As SqlConnection = New SqlConnection
    '
    conn.ConnectionString = <string from web.config>
    Dim cmd As SqlCommand = New SqlCommand
    cmd.CommandText = "SELECT AnagId, AnagName FROM [dbo].[SomeTableOrView] WHERE AnagName LIKE @SearchText + '%'"
    cmd.Parameters.AddWithValue("@SearchText", prefixText)
    cmd.Connection = conn
    conn.Open()
    Dim lstAgencies As List(Of String) = New List(Of String)
    Dim sdr As SqlDataReader = cmd.ExecuteReader
    While sdr.Read
        lstAgencies.Add(AjaxControlToolkit.AutoCompleteExtender.CreateAutoCompleteItem(sdr("AnagName").ToString, sdr("AnagID").ToString))
    End While
    conn.Close()
    Return lstAgencies
End Function

The trick is done using the CreateAutoCompleteItem method as in the above code.

In the javascript code we can use e.get_value for the ID and e.get_text() for the name (“AnagName” in the sample).

Categories: .NET, Ajax, Javascript, VB.NET, Vs2010

Breeze SaveChanges issue with Ie10

Every new technology is an hard path , an high mountain to climb.

I’m working to an MVC project with ASP.NET 4.5 , Breeze and Angular, authentication done with the new ASP.NET Identity, Entity Framework 6.

The big problem , for the average developer (me) , is:

  • For Microsoft, on MSDN you can find a lot of verbose pages about the syntax: an simple CRUD example using these technologies , add update delete of records, is hard to find.
  • For thirdy parties technologies you can find abstruse , overcomplexed samples ; but still there is a lack of a simple table management with CRUD operations.

In every case the samples simply ignore (at the least the ones that i discover with Google) the everyday questions with concurrency : another user could have deleted the record that you are trying to save, or you are trying to save a record of a table where there is an unique index for a field.

Ok , this was the rant, now the problem.

The code is about the update of a record in a Vehicle Classes table (ClassiMezzi).

Using Breeze, in a BreezeController (a class decorated with “[BreezeController]”) the typical sample for an update is

[HttpPost]
public SaveResult SaveChanges(JObject saveBundle)
{
    return _repo.SaveChanges(saveBundle);
}

This works with Ie11, the latest Firefox, Chrome, Safari; _repo is an repository object that at the end calls the Entity Framework context provider (in our sample EFContextProvider<ClassiMezziContext>)

But if you try to use this method for adding a new record on a table where there is an index UNIQUE, there are troubles with the error messages using Internet Explorer 10.

For these new apps i simply don’t consider Ie < 10; if you are still using Ie6 , Ie7.. time to upgrade …but to ignore the previous version is a bit excessive, for me: i test everything with the latest Firefox, Chrome also under Linux Android and Mac, for Ie to test the latest version and the previous seems reasonable.

And so, testing the code with multiuser issues (record deleted from another user, check constraints, UNIQUE indexes) i discovered the problem.

From the javascript side , the controller is called with an Ajax call with callbacks for success or failure:

function saveFailed(error) {
    try {
        manager.detachEntity(service.ClasseMezzoEntity);
    }
    catch (ex) { }
    service.ClasseMezzoEntity = null;
    if (error.httpResponse.status === 401 || error.httpResponse.status === 403) {
        logError(localize.localizeText("UnauthorizedOperation"), null, true);
        return;
    }
    //var strMsg = breeze.saveErrorMessageService.getErrorMessage(error).toLowerCase(); old version ok with Ie11 , not Ie10
    var strMsg = error.httpResponse.data.Errors[0].toLowerCase();
    if (strMsg.indexOf("entities may have been modified or deleted since entities were loaded") > 0) {
        logWarning(localize.localizeText("AnotherUsrHasChangeData"), null, true);
        return;
    }
    if (strMsg.indexOf("ix_classimezzi") > 0) {
        logWarning(localize.localizeText("ClassNameDuplicated"), null, true);
        return;
    }
    if (strMsg.indexOf("errdelete:anotheruser") > 0) {
        logWarning(localize.localizeText("AnotherUsrHasDeleteData"), null, true);
        return;
    }
    //  other code for unknown error management
    var msg = breeze.saveErrorMessageService.getErrorMessage(error);
    // etc.
};
 
function saveClasseMezzo() {
    service.isSaving = true;// see at the end of this module
    service.lastSaveSuccess = true;
    return manager.saveChanges()
        .catch(saveFailed)
        .then(saveClasseMezzoAttemptDone);
};

For example we try to add another ClasseMezzo (Vehicle class) with the same name of an existing item on the the db table.

With Ie11 and other latest browsers the first controller code is ok , and if our ajax call fails because the UNIQUE index the first

var strMsg = breeze…. commented code works perfectly.

But while with Ie11 etc. the error.message in javascript contains the string “Cannot insert duplicate key row …” with the index name that helps to manage the error (in this sample the index name is “ix_classimezzi”) in Ie10 we get


That is an anonymous “An error has occurred” that can’t help to manage the error, and no trace of the original SQL error in httpResponse: a big trouble if i would to say to the customer “you are trying to insert an duplicate item” because could be another thing , for example an SQL check violation.

The problem is the simple approach in the WebApi2 controller.

In order to manage correctly the errors coming from multiuser issues the WebAPI2 controller code must be:

[HttpPost]
[Authorize(Roles = "PowerUser")]
public SaveResult SaveChanges(JObject saveBundle)
{
    SaveResult objRes = null;
    List<object> objErr = null;
    var classimezziList = JsonConvert.DeserializeObject<List<ClassiMezzi>>(saveBundle.SelectToken("entities").ToString());
    try
    {
        objRes = _repo.SaveChanges(saveBundle);
        return objRes;
    }
    catch (Exception ex)
    {
        var keyMappings = new List<KeyMapping>();
        objErr = new List<object> { JsonConvert.SerializeObject(ex.Message) };
        return new SaveResult()
        {
            Entities = classimezziList.Cast<object>().ToList(),
            Errors = objErr,
            KeyMappings = keyMappings
        };
    }
}

Let’s examine the code.

We pass to the controller an JSON object , that we want deserialized in order to correctly manage the error; note in the catch management code: is explicitly created an SaveResult object , instead of the automatic creation.

Setting the Errors field to something != null , in our case the error message serialization, we cause the call of the javascript failure callback , and in the javascript failure code the message must be read as

var strMsg = error.httpResponse.data.Errors[0].toLowerCase();

because when the code is falling back we have this , now:


So we can manage the things as in the next rows.

This is valid also for Ie11 , and Firefox, Chrome under Windows, Linux, Mac, Android.

At least, this is the situation with the current latest Breeze (1.3.8)

jQuery not working in MVC4 (JSON undefined)

After a long time i’m coming back to work in MVC, obviously MVC4 with Entity Framework 5.

I updated an old project started from the classic Internet application template, the first thing was to update the packages via NuGet so the current jQuery version in my project is 2.03

First launch, and surprise: apparently jQuery is not working; this on my HP laptop with Windows 7 64 bit, Internet Explorer 10.

From the other hand also working in SharePoint i encountered problems with the jQuery upgrading

I tried to create a completely new MVC4 project, launched the project activating the debug in Ie and immediately popped out an error in jQuery:


JSON is undefined ??

With this error, JQuery naturally is not loaded:


I noticed that was active the icon for the compatibility view


By activating the compatibility voilà jQuery working..ok but i can’t’ tell to the customers to activate the compatibility…

The solution is to add in the MVC _Layout.cshtml file this line in the <head> section:

<meta http-equiv="X-UA-Compatible" content="IE=edge;chrome=1" />

With this correction jQuery is working and the compatibility icon is no longer present.

Categories: Javascript, JQuery, MVC, Vs2012

Transform legacy js in Module Pattern

Tipically a lot of existing javascript code is written in the old plain mode:

function cmdAddNew_onClick() {
    showEditBox('0');
}

Every javascript programmers know that this spaghetti-code approach is hard to maintain, every function is public so it is easy to have strange behaviors that is a nightmare to debug.

The worst problems are in portals as the old Aqualogic, where was easy to have portlets with a different behavior is you inverted two portlets position in the page…

A better approach is to write our modules using a modern pattern as the Module pattern or the Singleton, there is lot a documentation about searching on Google, for examples this and this.

A skeleton of a correct Module Pattern implementation could be:

$(document).ready(function ($) {
    mycomsite.customername.projectname.context = (function ($) {
        "use strict";
        
        var cmdAddNew_onClick = function () {
            showEditBox('0');
        };      
        
        var showEditBox = function (id) {
            // js code for showing an Jquery dialog...
        }
        
        var numericSetvMax = function () {
            return '100';
        };        
        
        return {
            cmdAddNew_onClick: cmdAddNew_onClick,
            numericSetvMax: numericSetvMax            
        };
    }(jQuery));
});

The main trick is the namespace; note that we pass the jQuery variable to the module in order to use jQuery inside the module.

For example if we are a IT company with a internet site www.acme.com , we are working to a tickets solution for the scandinavian KLM company and the current is the js file for the ticketing by VISA ,a namespace could be

$(document).ready(function ($) {
    comacme.klm.ticketing.visatickets = (function ($) {
...

But we can’t have js variables with “.” in javascript, you say … the trick is a initial first module of our web app with lines as these :

if (!comacme) {
    var comacme= {};
}
comacme.klm= {};
comacme.klm.ticketing = {};

So we have defined a pseudonamespace.

Using the Microsoft Ajax Toolkit there is a better support for the namespacing , with the Toolkit we have an object named Type and we can use the method Type.registerNamespace :

<script type="text/javascript">
function pageLoad(sender, args) {
    Type.registerNamespace('comacme.klm.ticketing.visatickets');
    window.alert(Type.isNamespace(comacme.klm.ticketing)); //displays 'True'
    window.alert(Type.isNamespace(comacme.klm.ticketing.Samples.Test)); //displays 'False'
    var namespaces = Type.getRootNamespaces();
    for (var i = 0, length = namespaces.length; i < length; i++) {
        window.alert(namespaces[i].getName()); //displays 'Sys' and  other namespace components
    }
}
</script>

Ok , but we don’t use the Ajax Toolkit so let’s go back to our sample.

In the return section we indicate the public names, so cmdAddNew_onClick and numericSetvMax (functions, but also variables) are public , showEditBox is private and cannot be accessed.

That is in another javascript module we can write

comacme.klm.ticketing.visatickets.cmdAddNew_onClick();

and it works , instead

comacme.klm.ticketing.visatickets.showEditBox('0);

gives error, the internal routine is not reachable.

The variables declared correctly (with “var ” in front) remains private in the module.

This encapsulation , if well used, leads to a structure more maintainable.

Ok , but our original problem is that we could have a lot of existing js code written in the spaghetti-mode, how to convert this code to the module pattern with a minimal effort?

The Regular Expression in the Visual Studio 2012 search & replace is our tool.

We can convert an existing js file with a lot of function to the module pattern with these three couples of regexp expressions for search & replace:

^(function)
var

^(var)(.*)(\()
$1$2 = function $3

^(})(.*)
$1;$2

Open the js file (better is you separe the js code form the aspx pages…) in Visual Studio, in Search & Replace (ctrl+h) click on Regular Expression


And write the first two search%replace strings:


Note that Visual Studio 2012 gives an preview of what the search expression has found (and will be replaced):


After the three search & replace we can see something as:


The last operation is to copy the converted code in the namespace structure (below “use strict”, in practice) , inserting in the “return” part the routines names that must be public.

After, there is a tedious work of searching for the original calls (must be added the namespace part) : but this effort should resolve a lot a problems.

Categories: .NET, Ajax, Javascript, JQuery, Vs2012

Unable to get property _focusTabbable

I was working with an .NET 4.5 project which was using jQuery 1.9.0, my js declarations was

<script src="http://code.jquery.com/jquery-1.9.0.min.js" type="text/javascript"></script> 
<script src="http://code.jquery.com/ui/1.10.0/jquery-ui.min.js" type="text/javascript"></script> 

Giving the fact that upgrading the jQuery version in an existing project is risky (see this my old post) i left the old declarations, but when i was verifying that all works as expected (inserting duplicated data) there was this error by clicking on every jQuery button after closing a jQuery dialog:

“Unable to get property ‘_focusTabbable’ of undefined or null reference”

This happened when my js code was simply displaying a message in a JQuery dialog: my work is to change the old , simple, plain “window.alert” with something better as user interface (remember that window.alert is really modal, the code is really stopped at the window.alert: with a modal jQuery dialog no!)

Effectively the Internet Explorer debugger shows the problem:


the object .data(“ui-dialog”) is null so the method ._focusTabbable() fails.

I found nothing of useful by googling, so i tried the very latest jQuery versions (at this moment, April 2013)

<script src="http://code.jquery.com/jquery-1.9.1.min.js" type="text/javascript"></script> 
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.min.js" type="text/javascript"></script>

With these latest versions of JQuery and jQueryUI the problem is disappeared, and it seems that there no problems related to the jQuery upgrade.

Categories: .NET, Javascript, JQuery

Ajax on Aqualogic portals

I’m no more (for now…) working on Aqualogic Portals: i began years ago , when the original publisher was Plumtree, then the technology was buyed from BEA and Oracle subsequently acquired BEA; i don’t know of new releases.

I wrote many “portlets” (originally termed “gadgets”) so i have fighted with the well knowed (from Aqualogic developers) javascript problem : that is your javascript routines in the Aqualogic environment (in the portlet code, in practice) are “gatewayed”.

The “gatewaying” was a strange feature: you wrote your javascript functions and behind the curtains the functions names at runtime were changed adding a special marker $$PORTLET_ID$$ if not present in the function name: an internal identifier for the current portlet.

Another trick to know: if you was using the same id for an html object in more than one portlet it was necessary to add the special marker so the portlet javascript code can refer the correct object , for example

<span id="spaWorkingImage$$PORTLET_ID$$" style="display: none">

At runtime examining the page source the marker was substituted with numbers; this caused a wide array of problems, first of all there was no thirdy party Ajax library (Microsoft Ajax toolkit for example) usable : the first call was working , the subsequent calls no because the gatewaying was changing under the covers the routines names.

Obviously was promised to a customer the use of “not page refreshing” features… so i struggled in order to find a solution.

I went to a minimal but working solution , that could be still useful to someone.

My approach was to write minimal ajax routines using the $$PORTLET_ID$$ , and this is the latest code written from me five years ago:

<script type="text/javascript" language="javascript"> 
var objAquaXHttp; 
 
function getXmlHttpObject$$PORTLET_ID$$(){ 
    try{
        objAquaXHttp = null;
        if (typeof XMLHttpRequest != "undefined"){
            objAquaXHttp = new XMLHttpRequest();
            return objAquaXHttp;  
        }
        else if (window.ActiveXObject){              
            var aVersions = ["MSXML2.XMLHttp.5.0","MSXML2.XMLHttp.4.0","MSXML2.XMLHttp.3.0","MSXML2.XMLHttp","Microsoft.XMLHttp"];
            for (var i = 0; i < aVersions.length; i++) {
                try {
                    objAquaXHttp = new ActiveXObject(aVersions[i]);
                    return objAquaXHttp;
                } catch (ex2) {
                    //Do nothing
                }
            }
            throw new Error("MSXML not installed.");        
        }                      
    }
    catch(ex){
        var strErr = "\nError getXmlHttpObject$$PORTLET_ID$$ num.: " + (ex.number & 0xFFFF);
        strErr += "\nDescription: " + ex.description;
        window.alert(ex + strErr);
    }                                                   
} 
 
function stdAjaxCall$$PORTLET_ID$$(urlWithQueryString, callbackFunction){ 
    try{
        objAquaXHttp = getXmlHttpObject$$PORTLET_ID$$();
        if (objAquaXHttp == null){
            window.alert ("Browser does not support HTTP Request.");
            return;
        } 
        objAquaXHttp.onreadystatechange = callbackFunction;
        objAquaXHttp.open("GET", urlWithQueryString, true);
        objAquaXHttp.send(null);                   
    }
    catch(ex){
        var strErr = "\nError stdAjaxCall$$PORTLET_ID$$ num.: " + (ex.number & 0xFFFF);
        strErr += "\nDescription: " + ex.description;
        window.alert(ex + strErr);
    }                                                   
}         
 
function stdAjaxCallPOST$$PORTLET_ID$$(urlWithoutQueryString, xmlString ,callbackFunction){ 
    try{
        objAquaXHttp = getXmlHttpObject$$PORTLET_ID$$();
        if (objAquaXHttp == null){
            window.alert ("Browser does not support HTTP Request.");
            return;
        } 
        objAquaXHttp.onreadystatechange = callbackFunction;
        objAquaXHttp.open("POST", urlWithoutQueryString, true);
        objAquaXHttp.setRequestHeader("Content-Type", "application/x-www-form-urlencoded") ;
        objAquaXHttp.setRequestHeader("Content-length", xmlString.length) ;
        objAquaXHttp.setRequestHeader("Connection", "close") ;
        objAquaXHttp.send(xmlString);                   
    }
    catch(ex){
        var strErr = "\nError stdAjaxCallPOST$$PORTLET_ID$$ num.: " + (ex.number & 0xFFFF);
        strErr += "\nDescription: " + ex.description;
        window.alert(ex + strErr);
    }                                                   
}             
 
function getXMLFromResponse$$PORTLET_ID$$(objHttp){
    var y;
    var x = objHttp.responseXML.childNodes;
    for (i = 0 ;i < x.length; i++){ 
        if (x[i].nodeType == 1){ 
            y = x[i].childNodes;
            for (j = 0; j < y.length; j++){ 
                if (y[j].nodeType == 1){ 
                    if (y[j].childNodes[0] != null)
                        return y[j].childNodes[0].nodeValue;
                    else
                        return '';
                } 
            }
        }
    }       
}        
</script>

Is very plain Javascript because i tried to use the module pattern, but there were strange problems.

This code define a public variable objAquaXHttp , and was calling aspx pages with an XML response type , used as pseudo web services.

In order to create a call we need the address, for example

var strUrl = CurrentAppPath$$PORTLET_ID$$ + "GetCitiesByProv.aspx" + "?q=" + encodeURIComponent(window.document.getElementById('listProvince').value);

In this case we obtain the path of the GetCitiesByProv.aspx and assigned to the variable CurrentAppPath$$PORTLET_ID$$, and then we create the complete url for a GET call.

But the real solution, the silver bullet, is the call (in the example from a onBlur event on a textbox):

stdAjaxCall$$PORTLET_ID$$(PTPortlet.getPortletByID($$PORTLET_ID$$).transformURL(strUrl), txtCityOnBlurCallback$$PORTLET_ID$$);

Note the transformURL: it does the magic of change the url as the Aqualogic portal does; and as in any async Ajax call we assign a callback (txtCityOnBlurCallback$$PORTLET_ID$$)

The pseudo web services is simple, the aspx is only a header:

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="GetCitiesByProv.aspx.cs" Inherits="ShipperPickupAddressDetails.GetCitiesByProv" %>

And the code behind , in my sample (is ASP.NET 2.0… no Task, async, await…):

using System;
using System.Data;
using System.Configuration;
using System.Collections;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
using System.Text;
 
namespace ShipperPickupAddressDetails
{
    public class GetCitiesByProv : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Clear();
            Response.ContentType = "text/xml";
            Response.ContentEncoding = Encoding.UTF8;
            System.Xml.XmlTextWriter objX = new System.Xml.XmlTextWriter(Response.OutputStream, Encoding.UTF8);
            objX.WriteStartDocument();
            //
            objX.WriteStartElement("root");
            //   Request.Form.ToString() if POST call
            theDataLayer objData = new theDataLayer();
            string strProvince = Request.QueryString["q"].ToString();
            const string strChar39 = "'";
            const string strApo = "'";
            try
            {
                DataSet ds = objData.ReadCity(strProvince);  
                DataTable dt = new DataTable();
                DataColumn dc = new DataColumn("City");
                dt.Columns.Add(dc);
                DataRow dr = null;
                StringBuilder objString = new StringBuilder();
                // add cities of province in a datatable
                if (ds != null && ds.Tables[0].Rows.Count > 0)
                {
                    dr = dt.NewRow();
                    dr[0] = "Select...  ";
                    dt.Rows.Add(dr);
                    for (int i = 0; i < ds.Tables[0].Rows.Count; i++)
                    {
                        string city = ds.Tables[0].Rows[i]["City"].ToString();
                        if (!city.Equals(String.Empty))
                        {
                            dr = dt.NewRow();
                            dr[0] = city;
                            dt.Rows.Add(dr);
                        }
                    }
                }
                string strItem = String.Empty;
                objString.Append("<select id='listCitySugg'  style='width: 250px;' onchange='javascript:listCitySugg_onchangeAddressManager(this);'>");
                foreach (DataRow objRow in dt.Rows)
                {
                    strItem = objRow[0].ToString().Replace(strApo, strChar39);
                    objString.Append("<option value='" + strItem + "'>" + objRow[0].ToString());
                }
                objString.Append("</select>");
                objX.WriteElementString("data", objString.ToString());
                //
                objX.WriteEndElement();
                objX.WriteEndDocument();
                objX.Flush();
            }
            catch (Exception)
            {
                throw ;
            }
            finally()
            {
                if(objX  != null)
                   objX.Close();
                Response.End();     
            }
        }
    }
}


The code outputs an XML string, which is managed from the callback:

function txtCityOnBlurCallback$$PORTLET_ID$$(){ 
    try{  
        if (objAquaXHttp.readyState == 4 || objAquaXHttp.readyState == "complete"){ 
            if (objAquaXHttp.status == 200) {
                var strResult = getXMLFromResponse$$PORTLET_ID$$(objAquaXHttp);             
                window.document.getElementById('spaListCitySugg').innerHTML = strResult ;
            }else{
                window.alert('Error in txtCityOnBlurCallback, status = ' +  objAquaXHttp.status);
            }
        } 
    }
    catch(ex){
        var strErr = "\nError txtCityOnBlurCallback num.: " + (ex.number & 0xFFFF);
        strErr += "\nDescription: " + ex.description;
        window.alert(ex + strErr);
    }                   
}  

So we can dinamically inject html code into a div and generate on the fly without a postback cities items of a province into an html select, in our Aqualogic portlet.

Categories: .NET, Ajax, Aqualogic, Javascript

HTML5 demo

At this address an interesting demo.

Categories: Javascript