Logging into the Infor CRM Mobile Client with an iPhone / iPad
Posted By: anoshwadia on June 29th, 2015 in Saleslogix, SLX Mobile
No Gravatar

We’re often asked, why can’t I login to the Infor CRM (SalesLogix) mobile client using my iPhone / iPad?

Here are some common things to check on your device:

1) Click on Settings   unnamed

 

2) Click on Safari –> Under Privacy and Security –> Block Cookies –> Always Allow

Capture

 

3) Go one step back…now scroll down and click on Advanced –> Make sure Javascript is set to ON.

Capture

 

4) Clear your Safari cache. This is done in 2 steps:

a) Click on “Clear History and Website Data”. Tap the Clear button when prompted.

Capture         clear-safari-history-and-website-data

b) Click on Advanced –> Website Data –> Remove All Website Data

open-website-data

Usually these steps should let you login to the mobile client if you try again.

 

Assuming the above does not resolve your issue, here are some additional things to try:

a) Try logging into the Mobile client using your regular desktop browser on your computer (Chrome / Firefox). If you cannot login there, it indicates either an issue with either the version of the Mobile client being incompatible with your version of iOS, or an issue with your login.

b) Verify your login works by logging into the Infor CRM web client using your credentials. Often a concurrent user is locked out of the system due to several invalid login attempts and is not made aware of this when they login to the mobile client.

 

If you need further assistance, call our CRM support line at 1-877-777-9779 and we’ll be happy to help you!


Custom modal edit views on Saleslogix Mobile
Posted By: nicocrm on November 15th, 2013 in Saleslogix, SLX Mobile
No Gravatar

From time to time we have the need to prompt the user with more detailed information than what can be collected in a single field. For these cases we often resort to a modal dialog in the web client. However if you attempt to create a standard dojo dialog (dijit.dialog) in the mobile client, you will find many things don’t work quite right, especially when it is used on a mobile device (as opposed to testing in the browser). For the mobile there is a different paradigm which is to use a view to collect the information. This edit view takes the whole screen, but keeps context of the original view it was called from so clicking the “Accept” or “Cancel” button will send the user back to it. It’s similar to a modal dialog in concept but is better adapted to the small screen size. A good example is the address control in the default mobile client.

If you want to create your own custom “dialog” or edit view, you will need to create a custom view that represents the layout of your dialog, and a custom field subclassing EditorField (Sage/Platform/Mobile/Fields/EditorField) in which you will specify the custom view as the target view for the edit action. An EditorField is a Field that can be used in your custom view layouts (you know, the views where you use “createLayout” to define what is shown), as well as in customized layout for standard views. But it is also a dijit which means it can be used in “from scratch” views as well, if you define a view from the ground up in Javascript. Either way the process is the same:

  • The EditorField will specify a “view” property as the id of a view that was previously registered with the application (using App.registerView), OR override navigateToEditView to obtain a custom view instance (though do not that in that case the view must still be registered using App.registerView, and you must use App.getView at least once to retrieve it, or the navigation will not work properly – some of the initialization code takes place in those 2 methods)
  • In the EditorField, override formatValue to specify how the values are to be displayed on a single line
  • Add CSS rules for your field (the original rules in base.css are hard-coded for specific subclasses of EditorField
  • Optionally, use FieldManager.register to make the custom field available to standard views
  • If you need some custom handling on the values after they are returned from the edit view, you can override getValuesFromView and have it work with this.currentValue (representing the “dirty” values from the edit view) or this.validationValue (which represents values from all the fields from the view – but do note, this won’t include data for properties that are not bound to a field – this is important to remember if you are using a field to edit a complex object that has many hidden properties)
  • The edit view is a normal view, subclassing Edit (Sage/Platform/Mobile/Edit) – you use the createLayout method to specify the fields to be displayed

For a practical example, I created a custom field to be used in place of the “Account Name” edit field – it prompts the user for an edit reason and stores the name of the person doing the edit and the date it was made (this is a bit redundant since this part is tracked in the history table but it makes it more convenient to see on one screen, especially on mobile):

Detail View

Corresponding edit view:

Edit View

There is a little bit of logic – it won’t let the user change the name without entering a reason, and it won’t let them enter a reason unless they are changing the name. It also sets the edited by name / date automatically when the user edits the name. Almost everything is in the AccountNameEdit control:

define('demo/Fields/AccountNameEdit',
    ['dojo/_base/declare', 'dojo/_base/lang', 'dojo/on', 'Sage/Platform/Mobile/Fields/EditorField', 'Sage/Platform/Mobile/Edit', 'Sage/Platform/Mobile/FieldManager',
        'Mobile/Saleslogix/Views/Account/Detail'],
function (declare, lang, on, EditorField, Edit, FieldManager, AccountDetail) {
    var View = declare([Edit], {
        titleText: "Edit Account Name",

        // keep track of whether we have reset the reason field yet or not
        _reasonReset: false,

        createLayout: function () {
            return this.layout || (this.layout = [{
                name: 'AccountName',
                property: 'AccountName',
                label: 'account',
                type: 'text',
                notificationTrigger: 'keyup',
                validator: { fn: this._validateAccountName, scope: this }
            }, {
                name: 'AccountNameEditReason',
                property: 'AccountNameEditReason',
                label: 'edit reason',
                type: 'text'
            }, {
                name: 'AccountNameEditedBy',
                label: 'edited by',
                readonly: true,
                type: 'text'
            }, {
                name: 'AccountNameEditedDate',
                label: 'edited date',
                readonly: true,
                // setting the view to false prevent the user from going to the calendar
                // (even when setting the date control to readonly, it still responds to click events)
                view: false,
                type: 'date'
            }]);
        },

        startup: function () {
            this.inherited(arguments);

            this.fields.AccountName.onChange = lang.hitch(this, "_onAccountChange");
        },

        onShow: function () {
            this.inherited(arguments);
            // disable the reason until they make an edit
            this.fields.AccountNameEditReason.disable();
            this._reasonReset = false;
        },

        // when the account name is edited, reset the reason, date and edited by fields, and enable reason field
        _onAccountChange: function () {
            if (!this._reasonReset) {
                this.fields.AccountNameEditedBy.setValue(App.context.user.UserInfo.UserName);
                this.fields.AccountNameEditedDate.setValue(new Date());
                this.fields.AccountNameEditReason.setValue('');
                this.fields.AccountNameEditReason.enable();                
                this._reasonReset = true;
            }
        },

        _validateAccountName: function (val) {
            if (val != this.entry.AccountName) {
                if (!this.fields.AccountNameEditReason.getValue()) {
                    return "Please provide reason for edit";
                }
            }
            return false;
        }
    });

    var Control = declare([EditorField], {
        view: "demo_accountnameedit_editview",

        // don't forget this part - it will enable the properties set in this control to merge with the current object
        applyTo: ".",

        formatValue: function (val) {
            return val.AccountName;
        },

        navigateToEditView: function () {
            if (!App.getView(this.view))
                App.registerView(new View({ id: this.view }));
            this.inherited(arguments);
        }
    });

    // We must extend the account detail view to include the fields used by our custom field
    lang.extend(AccountDetail, {
        querySelect: AccountDetail.prototype.querySelect.concat([
            "AccountNameEditReason", "AccountNameEditedBy", "AccountNameEditedDate"
        ])
    });


    return FieldManager.register('demo_accountnameedit', Control);
});

The only thing left is registering this onto the account view, in ApplicationModule, similar to other customizations – note the ApplicationModule is requiring the AccountNameEdit file, which will in turn call FieldManager.register and make our custom field available to be used in layouts:

define('demo/ApplicationModule', 
    ['dojo/_base/declare', 'Sage/Platform/Mobile/ApplicationModule', './Fields/AccountNameEdit'],
function (declare, ApplicationModule, AccountNameEdit) {
    return declare("demo.ApplicationModule", ApplicationModule, {
        loadCustomizations: function () {
            this.inherited(arguments);

            this.registerAccountCustomizations();
        },

        registerAccountCustomizations: function () {
            this.registerCustomization('edit', 'account_edit', {
                at: function (row) { return row.name == 'AccountName' },
                type: 'replace',
                value: {
                    name: 'AccountName',
                    // pass the whole object - we'll need it to be able to populate the extra fields from the edit view
                    property: ".",  
                    label: "acct",
                    type: "demo_accountnameedit"                    
                }
            });
        }
    });
});

And finally the CSS for it:

/* this stylesheet rule is hardcoded according to the control type in base.css.  
  Here we add one for our field so that the button may be displayed properly.
*/
.row-edit[data-field-type="demo_accountnameedit"] > div >.button
{
    margin: 0;
    padding: 0 8px;
    position: absolute;
    right: 4px;
}

/* Match the padding on the "input-text" class, defined in base.css
  For some reason that class is not automatically applied to the input field for EditorField fields.
*/
.row-edit[data-field-type="demo_accountnameedit"] > div >input[type=text]
{
    padding: 8px 34px 8px 110px !important;
}

This is the bundle for this sample, if you want to take a closer look.


Custom KPI & Hashtag Queries on Saleslogix Mobile 3.0
Posted By: nicocrm on November 1st, 2013 in Saleslogix, SLX Mobile
No Gravatar

KPI and Hash tag queries are 2 new exciting features of Mobile 3.0. Together they allow convenient adhoc reporting in the field. In this post I will review how to add your own queries and KPI for a custom entity (SalesOrder – which is actually a stock Saleslogix entity, but is not available by default on the mobile client). This assumes familiarity with how to develop your own custom module and defining the list view for it. This is still a new feature for me so I won’t go too deep in the details, specifically I won’t go over configuration of the chart. Also, this is taken from a live customization so some of the properties are not available on the default Saleslogix entity – you’ll have to adapt to your own use case of course.

Hash tag queries are easy – you just need to define the hashTagQueries property on your list class:

hashTagQueries: {
   'shipped-this-week': 'ActualShipDate ge "' + dateAdd(-7) + '"',
   'shipping-this-week': 'DatePromised le "' + dateAdd(7) + '" and ActualShipDate eq ""',
   'shipping-today': 'DatePromised lt "' + dateAdd(1) + '" and ActualShipDate eq ""',
   'pending': 'Status like "Pending%"',
   'shorted-this-week': 'ActualShipDate ge "' + dateAdd(-7) + '" and OrderShorted eq true'
},

Instead of a string you can also use a function that returns a string if it needs to be dynamic. As you can see the queries are simply hard coded in the class and whatever is made available there, will be available for the user to select.

KPIs are a little bit more involved because there is a degree of user configurability – the metric that they select stay enabled between sessions and when they do a different query, so this is stored in the application preferences, which are stored in the browser’s localStorage (as a side note you can review the localStorage for the current site on the Chrome developer tools “Resources” tab). In fact the entire metric definition is stored in the preferences, not just the configurable part. This is important to know because it means that your users will have to clear their preferences in order to get the updated metric definition. For development purposes, the most convenient way I found to do it was to use a Chrome “incognito” window (aka “porn mode”) and just close it and open a new one when I want a full reload.

As for how the KPIs are actually defined, this is also a bit different because of where they are loaded – you can’t just add the definition to your list class. Similar to how we have to hack the Saleslogix Application object to change the default views definition, its “setDefaultMetricPreferences” method needs to be extended inside of your custom module’s loadCustomizations method. For example using the dojo/aspect module:

aspect.after(SalesLogixApplication.prototype, "setDefaultMetricPreferences", function () {
    var metrics = this.preferences.metrics;
    if (!metrics.salesorders) {
        metrics.salesorders = [
        {
            "title": "Total Cases",
            "queryName": "executeMetric",
            "queryArgs": {
                "_filterName": "AccountManager",
                "_metricName": "TotalQuantityMetric"
            },
            "metricDisplayName": "Total Cases",
            "filterDisplayName": "Vendor",
            "chartType": "bar",
            "aggregate": "sum",
            "formatter": "bigNumber",
            "enabled": false
        },
        {
            "title": "Total Lbs",
            "queryName": "executeMetric",
            "queryArgs": {
                "_filterName": "AccountManager",
                "_metricName": "TotalWeightMetric"
            },
            "metricDisplayName": "Total Lbs",
            "filterDisplayName": "Vendor",
            "chartType": "bar",
            "aggregate": "sum",
            "formatter": "bigNumber",
            "enabled": false
        },
        {
            "title": "Total $",
            "queryName": "executeMetric",
            "queryArgs": {
                "_filterName": "AccountManager",
                "_metricName": "OrderTotalMetric"
            },
            "metricDisplayName": "Total $",
            "filterDisplayName": "Vendor",
            "chartType": "bar",
            "aggregate": "sum",
            "formatter": "bigNumber",
            "enabled": false
        }
        ];
        this.persistPreferences();
    }
});

A few things to note:

  • - both “_filterName” and “_metricName” must be defined inside of “queryArgs”, otherwise the query will not run
  • - the “enabled”: false refers to the default state of the widget – i.e. whether it’s selected by default or not
  • - _metricName refers to a metric defined in AA (under filters, similar to how we do it for SlxClient dashboards)
  • - _filterName is the group that is used on the chart. This needs to be chosen carefully because if you select a filter that is based on ranges, the server will send 1 query per range to the database!! For example if you select AccountName as filter, and the AccountName filter is defined using a range of 1 letter as is common, the database will have to run 27 queries, obviously this is terrible for performance when we only need to get the total. If you select a filter based on distinct, then the system will send a “group by” query, this is still some extra work to be performed by the database (and then on the client since we have to sum the results to display only the total), but at least it’s only 1 query. So basically choose your filter well (I am sure in the near future the widget will be optimized to not send the filter name for the totals display but for now it is what it is)
  • - “chartType”, “aggregate” and “formatter” are in theory extension points where you could load your own module to perform the calculation but I have not had the chance to mess with them yet

That’s it for now, hope it gives you a head start.


Defaulting Picklists in the SLX Mobile Client
Posted By: Alex.Cottner on October 1st, 2012 in General, Javascript, SLX Mobile
No Gravatar

I had a request that required me to default several picklist fields throughout the SLX Mobile client. Normally this would mean hard coding the fields to populate with a certain value. But I wanted the client to be able to manage their default picklists values by marking items as “Default” in the client, just like they always have. After looking at the picklist control I realized this was actually pretty simple to do. All I needed to do was add a couple methods to the picklist object to pull down and set the default values, then I could call this from the “applyContext” methods on the forms I wanted this on.
(more…)