Записки на лету

All posts tagged JavaScript

На днях обнаружил быстрый способ узнать EntityTypeCode по имени сущности

var accountEntityTypeCode = Mscrm.EntityPropUtil.EntityTypeName2CodeMap["account"];

 


С версии 2013 появилось несколько новых методов нотификации пользователя при работе на форме. Расскажу про них и в каких случаях можно их использовать.

1. Поддерживаемая версия alert().

Раньше мы использовали стандартный alert("Сообщение!");

Теперь у нас есть два варианта:

  1. Простое сообщение: Xrm.Utility.alertDialog(message,onCloseCallback);
    где onCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на загрузке формы заполнение поля и если оно не заполнено то просить его заполнить и ставить в него фокус):

    function formOnLoad() {
        /// <summary>Функция, вызываемая при загрузки формы</summary>
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.alertDialog("Не заполнен мобильный телефон",
                function () {
                    Xrm.Page.getControl("mobilephone").setFocus();
                });
        }
    }

    AlertMessage

  2. Диалог подтверждения: Xrm.Utility.confirmDialog(message,yesCloseCallback,noCloseCallback);
    где yesCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения и noCloseCallback колбэк функция выполняемая после нажатия кнопки ОК окошка сообщения.
    Пример использования (Проверять на сохранении формы заполнение поля и если оно не заполнено то предложить его заполнить и ставить в него фокус):

    function formOnSave(context) {
        /// <summary>Функция, вызываемая при сохранении формы</summary>  
        if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Utility.confirmDialog("Не заполнен мобильный телефон. Заполнить?",
                // Если нажали ОК
                function () {
                    // Ставим фокус
                    Xrm.Page.getControl("mobilephone").setFocus();
                    //Отменяем сохранение
                    var eventArgs = context.getEventArgs();
                    eventArgs.preventDefault();
                },
                // Если нажали Cancel
                function () {
                    // Ничего не делаем
                });
        }
    }

    AlertDialogMessage

2. Панель нотификации.

Тоже самое можно реализовать с помощью панели нотификации.

Xrm.Page.ui.setFormNotification(message, level, uniqueId);

где:

  • message (string) — сам текст сообщения.
  • level (string) — тип иконки, возможные варианты: «ERROR» , «WARNING», «INFO».
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

При добавлении исполнении следующих строк:

Xrm.Page.ui.setFormNotification('Произошла ошибка...!', 'ERROR', 'id1');
Xrm.Page.ui.setFormNotification('Предупреждение...', 'WARNING', 'id2');
Xrm.Page.ui.setFormNotification('Для информации', 'INFORMATION', 'id3');

Мы увидим следующее.

NotificationMessages

Но тут необходимо помнить, что свое сообщение необходимо скрывать явно, т.к. иначе оно будет висеть до перезагрузки формы. Для чего нам и необходим третий параметр uniqueId, без него мы сможем только скрыть все сообщения, а на форме могут быть еще другие сообщения, как наши так и системные.

Скрываются сообщения так:

Xrm.Page.ui.clearFormNotification('id1');  // Сообщение с идентификатором 'id1'

Попробуем реализовать пример выше с использованием понели нотификации. Нам понадобятся две функции, на загрузку формы formOnLoad() и на изменение поля Мобильный телефон mobilePhoneOnChange().

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.ui.setFormNotification("Не заполнен мобильный телефон", 'WARNING', "mobilephoneAlert");
    } else {
        Xrm.Page.ui.clearFormNotification("mobilephoneAlert");
    }
}

Выглядеть будет так.NotificationMessages2

3. Нотификация поля.

Третий вариант, который мы можем использовать это нотификация непосредственно на поле, что актуально при сообщении касающегося какого-то конкретного поля. Это очень похоже на предыдущий вариант:

Xrm.Page.getControl(fieldName).setNotification(message, uniqueId);  // Показать сообщение
Xrm.Page.getControl(fieldName).clearNotification(uniqueId);         // Скрыть сообщение

где:

  • fieldName (string) — имя поля.
  • message (string) — текст сообщния.
  • uniqueId (string) — идентификатор сообщения (используется для убирания конкретного сообщения).

Выглядит так:NotificationMessages3

Кстати, пока Вы видите такое сообщение CRM не даст Вам сохранить форму.

Соответственно, наш пример будет выглядеть так:

function formOnLoad() {
    /// <summary>Функция, вызываемая при загрузки формы</summary>
    if (Xrm.Page.getAttribute("mobilephone") && !Xrm.Page.getAttribute("mobilephone").getValue()) {
        Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
    }
    else {
        Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
    }
}

function mobilePhoneOnChange() {
    /// <summary>Функция, вызываемая при изменении поля телефон</summary>
    if (Xrm.Page.getAttribute("mobilephone")) {
        if (!Xrm.Page.getAttribute("mobilephone").getValue()) {
            Xrm.Page.getControl("mobilephone").setNotification("Не заполнен мобильный телефон", "EmptyMobilephoneAlert");
        } else {
            Xrm.Page.getControl("mobilephone").clearNotification("EmptyMobilephoneAlert");
        }
    }
}

Такого же результата мы можем достигнуть создав «Бизнес-правило».AlertBusinessRule

В результате, CRM также добавит скрипт на форму, сгенеренный по этому правилу. Если кому интересно, ниже скрипт сгенеренный для приведенного правила.

 

function pbl_eb4eb395d711e61180ce00155de610f8() {
    try {
        var v0 = Xrm.Page.data.entity.attributes.get('mobilephone');
        var v2 = '';
        if ((v0 == undefined || v0 == null || v0 === ""))
        { return; }
        var v1 = v0.getValue();
        if ((v1) == undefined || (v1) == null || (v1) === "") {
            v0.controls.forEach(function (c, i) {
                c.setNotification(Mscrm.BusinessRulesScript.GetResourceString('e59db157-30ca-9aa2-05c3-d3da4ac312c0', 'Не заполнен мобильный телефон'), 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4');
            });
            v2 = v2 + 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4\x3b';
        }
        var v3 = [{ 'CId': 'mobilephone', 'SId': 'eb4eb395-d711-e611-80ce-00155de610f8SetMessageStep4' }];
        for (var i = 0; i < v3.length; i++) {
            var l1 = v3[i];
            if (v2.indexOf(l1.SId + '\x3b') === -1) {
                Xrm.Page.data.entity.attributes.get(l1.CId).controls.forEach(function (c, i) { c.clearNotification(l1.SId); });
            }
        }
    } catch (e) {
        Mscrm.BusinessRules.ErrorHandlerFactory.getHandler(e, arguments.callee).handleError();
    }
}
Mscrm.BusinessRulesScript.Initialize = function () {
    Mscrm.BusinessRulesScript.AttributesOnChangeHandlers = {};
    (function () {
        var onchangehandler = function () {
            pbl_eb4eb395d711e61180ce00155de610f8();
        };
        Mscrm.BusinessRulesScript.AttributesOnChangeHandlers['mobilephone'] = onchangehandler;
        var attributeObject = Xrm.Page.data.entity.attributes.get('mobilephone');
        if (attributeObject != null && attributeObject != undefined) {
            attributeObject.addOnChange(onchangehandler);
        }
    })(); pbl_eb4eb395d711e61180ce00155de610f8();
};

4. Всплывающая подсказка tooltip.

Также, следует помнить, что всплывающие подсказки для полей в CRM уже реализованы. По-умолчанию всплывающей подсказкой для поля является описание в настройках поля.AlertTooltipSettings

AlertTooltip

 


 Microsoft Dynamics CRM 2011 Scripting CookbookНа днях вышла новая книга Microsoft Dynamics CRM 2011 Scripting Cookbook, книга поосвящена клиентской разработке на JavaScript под MS Dynamics CRM 2011.

Автора книги Nicolae Tarla, это его первая книга. Его блога или еще какой информации я не нашел.

Я ее уже купил, хотя, почитать еще не успел.

Промо коды для покупки книг на сайте издательства (действительны до 20.04.13):

For 50% Off above eBooks | Use Code: EPMIC50
For 20% Off above Print Books | Use Code: EPMIC20
Offer valid till 20th April 2013

Все начинается с малого, как и скрипты на форме сущности, сначала одна функция, затем еще одна и т.д.

На одном проекте, я помню, дошло до того, что общий размер файлов скриптов пошел на мегабайты и количество функций перевалило за сотню. Все это находилось в разных файлах и писалось разными людьми и в результате всего этого так называемого «Спагетти кода» полезли ошибки, появлялись функции и переменные с одинаковыми именами и т.д.

В общем, хаос рос с объемом кода. 🙂

Чтобы решить эту проблему я задумался, какой же способ организации скриптов, называя более умными словами — паттерн, применить.

Сначала пришла мысль взять так называемый Неймспейс паттерн (Namespace Pattern), который используется, можно сказать, у нас прямо под носом, в примерах SDK.

Выглядит он так:

if (typeof (MYCODE) == "undefined")
{ MYCODE = { __namespace: true }; }
MYCODE.QuoteFormEvents = {
    //Приватные функции
    _doSomethingPrivate: function (param) {

    },
    //Публичные функции
    doSomethingPublic: function (param) {

    },
    __namespace: true
};

Грубо говоря, запихиваем функции в объект, который внутри другого объекта, которые олицетворяют пространства имен. На самом деле, приватные функции все равно доступны, также встает вопрос, где хранить приватные переменные, чтобы они были доступны только для приватных функций.

Для меня ответ пришел, когда я начал разрабатывать приложения для Windows 8 на HTML5 и JScript, а именно начал разбираться, как там принято писать на JScript. Естественно, в этих приложениях очень много скриптов, и также среда исполнения накладывает некоторые ограничения. Так вот, организованы они там по паттерну раскрывающийся модуль, мой вольный перевод — Revealing Module Pattern.

Немного переделанный для использования в CRM он выглядит так:

var QuoteFormEvents = function () {
    //Переменные
    var tempString = "";
    var userId = Xrm.Page.context.getUserId();

    //Функции
    function doSomething() {

    }

    function formOnLoad() {
        doSomething();
    }
    //Объявления
    return {
        UserId: userId,
        FormOnLoad: formOnLoad
    };
}();

Здесь также есть объекты, которые олицетворяют пространство имен, но суть в том, что мы весь наш код запихиваем в самовызывающуюся функцию, т.к. область видимости в JS определяется именно функцией. Внутри этой функции друг другу видны и переменные, и функции, а в конце мы объявляем только те функции и переменные, которые будут публичными, все остальное снаружи не видно.

Если Вы собираетесь написать что-то вроде библиотеки, которая будет использоваться в нескольких местах и для которой понадобится возможность переопределения функций, я бы предложил паттерн раскрывающийся прототип (Revealing Prototype Pattern) как более подходящий.

Про Revealing Module Pattern подробнее здесь: Revealing Module Pattern: Structuring JavaScript Code – Part III

Про Revealing Prototype Pattern подробнее здесь: Revealing Prototype Pattern: Structuring JavaScript Code – Part IV

 


Я думаю, что уже многие знают как это сделать, а для остальных и себя, в том числе, т.к. время от времени забываю как это сделать.

Итак, чтобы IntelliSence показвал нам функции и методы, предоставляемые нам скриптами CRM необходимо скачать и добавить файл XrmPage-vsdoc.js в проект и затем добавить на него ссылку в необходимый .js файл.

///<reference path="IntelliSense\XrmPage-vsdoc.js"/>

Продолжая эту тему добавляю так же ссылку на проект снипетов для студии CRM 2011 Code Snippets for Visual Studio.


Меня уже столько раз просили добавить скрипт на форму для запуска диалога или отчета с кнопки, что я решил разобраться с этой проблемой раз и навсегда.

Идея проста, написать общий скрипт для запуска диалога/отчета и дальше работать уже с только с CRM 2011 Visual Ribbon Editor, о котором я говорил ранее.

Итак, вот скрипт, в нем две функции, RunDialog() для запуска диалога и RunReport() для запуска отчета.

function RunDialog(dialogId, typeName, recordId) {
 /// <summary>Запускает диалог</summary>
 /// <param name="dialogId" type="String">GUID диалога (без скобок).</param>
 /// <param name="typeName" type="String">Имя сущности.</param>
 /// <param name="recordId" type="String">GUID сущности (без скобок).</param>
 var link = Mscrm.CrmUri.create("/cs/dialog/rundialog.aspx") + "?DialogId={" + dialogId + "}&EntityName=" + typeName + "&ObjectId=" + recordId;
 openStdDlg(link, null, 615, 480, false, false, null);
 window.location.reload(true);
 }
function RunReport(rdlName, reportGuid, entityGuid, entityType) {
 /// <summary>Determines the area of a circle that has the specified radius parameter.</summary>
 /// <param name="rdlName" type="String">Имя .rdl файла с отчетом.</param>
 /// <param name="reportGuid" type="String">GUID отчета (без скобок).</param>
 /// <param name="entityGuid" type="String">GUID сущности (без скобок).</param>
 /// <param name="entityType" type="String">Код сущности (без скобок).</param>
 var link = Mscrm.CrmUri.create("/crmreports/viewer/viewer.aspx") + "?action=run&context=records&helpID=" + rdlName + "&id={" + reportGuid + "}&records={" + entityGuid + "}&recordstype=" + entityType;
 openStdDlg(link, null, 800, 600, true, false, null);
 }

 

Сохраняем скрипт в CRM как веб-ресурс с названием new_ribbon, сохраняем и публикуем.

Предполагается, что у нас уже есть диалог и он активирован. Нам понадобится его Guid.

В моем случае мы добавляем кнопку на форму сущности Обращение (Incident).

Теперь запускаем Ribbon Editor и добавляем необходимую нам кнопку.

Переходим в раздел Action и жмем Add… -> JavaScriptFunction для добавления функции:

Вводим Function Name: RunDialog и Library: $webresource:new_ribbon
Теперь напротив только, что добавленной функции еще раз жмем Add… и добавляем следующие параметры:

  1. String Parameter AD119AC4-C589-4746-8AA2-AA74398B6C98
  2. String Parameter incident
  3. Crm Parameter -> FirstPrimaryItemId

Где 1 — Guid диалога, 2 — Имя схемы сущности и 3 — параметр, который является Guid-ом сущности с формы которой мы запускаем диалог.

Теперь жмем Save и, если мы все сделали правильно, наша кнопка будет вызывать диалог.
С отчетами все так же по аналогии.
И, что самое главное, теперь нам не надо никуда добавлять скрипты, на одну организацию достаточно одного скрипта. А это значит, что теперь этим могут заниматься Аналитики/Консультанты. 🙂


«Не удается выполнить программу из освобожденного сценария» («Can’t execute code from a freed script»)

Встретился вот с такой проблемой при работе с диалогом.
А конкретнее при использовании диалогового окна ServiceAppointment, которое должно вызываться по кнопке с нескольких сущностей я не смог получить даты.
Оказалось, что ссылочные типы переданные из диалога нельзя прочесть после закрытия диалога. И что самое удивительное, данные в дебаггере видно.
Большинство советов типа поменять браузер (это же MS Dymamics CRM) или передавать строки из диалога не работают, т.к. я использую диалоговое окно самой CRM и ничего с ним сделать не могу.
Решение оказалось простое.

Object.prototype.toString.call()

В моем случае с датой будет

Data.prototype.toString.call()

Следующая функция создает заметку.
Входящие параметры:
entity — EntityReference сущности к которой привязывается записка
subject — Заголовок заметки
text — Текст заметки
Функция GetOrganizationService() возвращает объект работы с данными в CRM 2011, была написана ранее, использует FetchXml (см. пример здесь Execute Fetch from JavaScript in CRM 2011).

function _createAnnotation(entity, subject, text) {
var orgService = GetOrganizationService();
var annotation = {};
annotation.Name = "annotation";
annotation._properties = [];
annotation._propertyTypes = [];
annotation._properties['objectid'] = entity;
annotation._propertyTypes['objectid'] = 'lookup';
annotation._properties['subject'] = subject;
annotation._propertyTypes['subject'] = 'string';
annotation._properties['notetext'] = text;
annotation._propertyTypes['notetext'] = 'string';
annotation._properties['isdocument'] = 'false';
annotation._propertyTypes['isdocument'] = 'boolean';
annotation._properties['mimetype'] = 'text/html';
annotation._propertyTypes['mimetype'] = 'string';
orgService.Create(annotation);
}

В этой статье я буду писать о своем опыте использования JavaScript в MS Dynamics CRM 2011.

Работа с полями

Во-первых, чтобы скрипт работал на форме его необходимо добавить в веб-ресурс и добавить этот веб-ресурс непосредственно на форму.

Получить значение текстового поля «telephone»

Xrm.Page.data.entity.attributes.get("telephone1").getValue();

Сохранить значение «123» в текстовое поле «telephone»

Xrm.Page.data.entity.attributes.get("telephone1").setValue("123");

Получить значение пиклиста «address1_addresstypecode»

Xrm.Page.data.entity.attributes.get("address1_addresstypecode").getValue(); //Цифровое значение
Xrm.Page.data.entity.attributes.get("address1_addresstypecode").getText(); //Текстовое значение

Имейте в виду, что если значение пиклиста не выбрано, то getValue()/getText() вернет «undefined».
Установить значение 2 пиклиста «address1_addresstypecode»

Xrm.Page.data.entity.attributes.get("address1_addresstypecode").setValue(2);

Установить значение поля с датой «birthdate» на сегодня

var today = new Date();
Xrm.Page.data.entity.attributes.get("birthdate").setValue(today);

Для выражения Xrm.Page.data.entity.attributes.get() есть сокращенный вариант Xrm.Page.getAttribute().
Если поле помечено на форме, что оно доступно только для чтения (ReadOnly) чтобы его изменить необходимо выставить ему SubmitMode = Always.

Xrm.Page.getAttribute("fieldName").setSubmitMode("always");

Отключить (Disable) поле «name»

Xrm.Page.ui.controls.get("name").setDisabled(true);

Скрыть (Hide) поле «name»

Xrm.Page.ui.controls.get("name").setVisible(false);

Скрыть (Hide) секцию в табе

Xrm.Page.ui.tabs.get("tabname").sections.get("sectionname").setVisible(false);

Работа с формой

Сохранить форму

Xrm.Page.data.entity.save();

Сохранить и закрыть форму

Xrm.Page.data.entity.save("saveandclose");

Остановить сохранение формы
Вешаемся на событие формы OnSave.
Обязательно надо поставить галочку напротив «Pass execution context as first parameter». Без этого не сработает.

function formOnSave(executionObj)
{
var shouldSave = true;
if (shouldSave) {
alert("Unable to save because of some reason or the other.");
executionObj.getEventArgs().preventDefault();
}
}

Закрыть форму
Пользователю будет показано напоминание если он не сохранил изменения.

Xrm.Page.ui.close();

Открыть форму создания
entityType — имя сущности (entity logical name).

Xrm.Utility.openEntityForm(entityType);

Открыть форму создания с заполненными полями
parameters — объект с параметрами,
entityType — имя сущности (entity logical name).

var entityType = "account";
var parameters = {};
// Заполняем лукап "accountid"
parameters["accountid"] = "b053a39a-041a-4356-acef-ddf00182762b";
parameters["accountidname"] = "Имя организации"
// Следующий параметр только для поля типа Клиент, где надо указать Контакт это или Организация
parameters["accountidtype"] = "account";
// Заполняем текстовое поле "name"
parameters["name"] = "Test";
// Заполняем пиклист "address1_addresstypecode"
parameters["address1_addresstypecode"] = "3";
// Заполняем булево поле Да/Нет  "donotemail"
parameters["donotemail"] = "1";
Xrm.Utility.openEntityForm(entityType, null, parameters);

Подробнее здесь Set Field Values Using Parameters Passed to a Form.
Открыть форму существующей сущности
id — GUID экземпляра сущности,
entityType — имя сущности (entity logical name).

Xrm.Utility.openEntityForm(entityType, id);

Определить тип формы
Значения тип форм (Form type codes):
Create — 1, Update — 2, Read Only — 3, Disabled — 4, Bulk Edit — 6.

Xrm.Page.ui.getFormType();

Получить GUID текущей записи

Xrm.Page.data.entity.getId();

Получить GUID текущего пользователя

Xrm.Page.context.getUserId();

Получить LCID текущего пользователя и организации

Xrm.Page.context.getUserLcid(); //For User
Xrm.Page.context.getOrgLcid(); //For Organization

Список всех LCID здесь.

Получить URL сервера CRM

Xrm.Page.context.getServerUrl();

С выходом 12-го роллапа функция устарела и теперь рекоммендуется делать так

Xrm.Page.context.getClientUrl();

а чтобы работало всегда то так

var ORG_URL = (typeof(Xrm.Page.context.getClientUrl) == "function") ? Xrm.Page.context.getClientUrl() : Xrm.Page.context.getServerUrl();

Получить имя организации

Xrm.Page.context.getOrgUniqueName();

Лукапы

Получить GUID значение лукапа «contactid»

Xrm.Page.data.entity.attributes.get("contactid").getValue()[0].id;

Получить текстовое значение лукапа «contactid»

Xrm.Page.data.entity.attributes.get("contactid").getValue()[0].name;

Функция, которая заполняет лукап
Параметры:
fieldName — название поля,
id — GUID экземпляра сущности,
name — название сущности, которое будет показано в лукапе,
entityType — имя сущности (entity logical name)

// Set the value of a lookup field
function SetLookupValue(fieldName, id, name, entityType) {
if (fieldName != null) {
var lookupValue = [];
lookupValue[0] = {};
lookupValue[0].id = id;
lookupValue[0].name = name;
lookupValue[0].entityType = entityType;
Xrm.Page.getAttribute(fieldName).setValue(lookupValue);
}
}

Данная функция заполняет первое и единственное значение лукапа. Если необходимо ввести несколько значений то следует добавить к массиву lookupValue необходимое количество объектов, по одному на каждый элемент в лукапе.

Тоже самое можно сделать с помощью JSON в одну строку, что выглядит, правда уже не так красиво.

Xrm.Page.data.entity.attributes.get(fieldName).setValue([{ id: id, name: name, entityType: entityType}]);

Открыть диалоговое окно
В CRM 2011 есть обертка для window.showModalDialog(), функция называется openStdDlg().

openStdDlg(sPath, oArgs, iWidth, iHeight, bResizable, bModeless, sCustomWinParams);

где:
sPath — ссылка на окно,
oArgs — объект с параметрами,
iWidth — ширина окна,
iHeight — высота окна,
bResizable — можно ли изменять размер окна,
bModeless — ,
sCustomWinParams — пользовательские параметры, передаваемые в окно.

Открыть форму сущности
Следующий пример открывает форму существующей сущности incident с GUID = 47c2bb7b-ba62-4afb-9b10-1a24c7a2799e в новом окне.

window.open(Xrm.Page.context.getServerUrl() +
"/main.aspx?etn=incident&pagetype=entityrecord&id=" +
encodeURIComponent("{47c2bb7b-ba62-4afb-9b10-1a24c7a2799e}"),
"_blank",
"location=no,menubar=no,status=no,toolbar=no",
false);

Обновить рибон
Обновляет только состояние Enable/Disable Rule;

Xrm.Page.ui.refreshRibbon();

Статья еще не закончена и, скорее всего, вряд ли будет, так как я буду ее пополнять по мере расширения моего собственного опыта.
Ждите обновлений…


Правильное понимание false значений

В отличие от языка C# в JavaScript имеется много значений кроме false, которые также расцениваются как false. Они называются False-y values и очень важно их понимать.

Эти значения это:

  1. false
  2. null
  3. undefined
  4. «»
  5. 0
  6. NaN (not a number)

Все остальные значения, включая «0», «false», и много других странных комбинаций рассматриваются как true.

Устанавливайте значения по умолчанию правильно

Обычно мы проверяем переменную на null перед использованием во избежание исключения «Object reference not set to an instance of an object».

Следующий пример типичен для языка C#.

Никогда не делайте так в JavaScript.

 var someString = "";
 if (someString != null && someString.length > 0) {
 someString = "Default value";
 }

Да и в C# тоже так лучше не делать. Для этих целей есть специальная функция.

 var someString = "";
 if (!string.IsNullOrEmpty(someString)) {
 someString = "Default value";
 }

Учитывая false-y значения упомянутые выше, в JavaScript следует делать так:

 if (someString) {
 someString = "Default value";
 }

В данном случае выполняется проверка на значения undefined, null, и empty string.

Еще один вариант у разработчиков C# может выглядеть так:

someString = someString ? someString : "Default value";

Он чуть получше но, все равно не верен.

Некоторые могут попытаться сделать даже так:

someString = someString ?? "Default value";

Что не поддерживается в JavaScript вообще.

В JavaScript, как ни странно, это должно выглядеть так:

someString = someString || "Default value";

Вывод: Если нам надо проверить переменную является-ли она undefined, null и т.д. или имеет значение.

Все, что нам надо сделать это проверить саму переменную.

Правильно используйте операторы сравнения

Операторы == и != в языке JavaScript попытаются сначала привести сравниваемые значения к одному типу перед сравнением, что при ведет к неожиданным результатам.

Т.е. следующие выражения возвращают true

console.log(0 == '') //Returns true
console.log(0 == '0') //Returns true
console.log(false == '0') //Returns true
console.log(null == undefined) //Returns true
console.log(0 == ' \t\r\n') //Returns true

Используйте операторы === и !== т.к. они так же делают проверку и на тип.

Т.е. следующие выражения возвращают false:

console.log(0 === '') //Returns false
console.log(0 === '0') //Returns false
console.log(false === '0') //Returns false
console.log(null === undefined) //Returns false
console.log(0 === ' \t\r\n') //Returns false

Объявляйте объекты и массивы правильно

Рабочее, но неправильное объявление

var person = new Object(),
keys = new Array();

Ключевое слово new было добавлено в JavaScript, от части, чтобы походить на классические языки, но на самом деле программистов оно больше запутывает чем помогает. В JavaScript есть более соответствующие языку способы объявить объекты и массивы, которые и следует использовать.

var person = {},
keys = [];
var person = {
    firstName: "Vasily",
    lastName: "Pupkin",
    fullName: function () {
        return this.firstName + " " + this.lastName;
    }
},
keys = ["123", "676", "242"];

Объявляйте все Ваши переменные используя «literal notation» вместо оператора new.

Определенно не следует использовать оператор new с переменными типа Boolean, Number и String или с функциями.

Используйте оператор new только когда Вы создаете объект и Вы хотите, чтобы он использовал свой конструктор.

Использование циклов

Цикл for…in в JavaScript не гарантирует порядок полученных элементов, так же Вы можете встретить ряд других проблем.

Например, объявим массив.

var myArray = [], name;
myArray[5] = "test";
console.log(myArray.length);

покажет 6, что, в принципе, вполне предсказуемо.

Создадим цикл…

for(var i = 0, length = myArray.length; i < length; i++){
    console.log(i, myArray[i]);
}

Результатом цикла будет:

//0, undefined
//1, undefined
//2, undefined
//3, undefined
//4, undefined
//5, test

Может возникнуть, так же, другая проблема. Предположим, что кто-то или что-то, скажем какая-нибудь библиотека, которую мы подключили, изменила прототип массива следующим образом.

Array.prototype.someVariable = "Some Variable";
for(name in myArray){
    console.log(name, myArray[name]);
}

Результатом цикла будет:

//5, test
//someVariable, Some Variable

Вместо цикла for…in следует использовать for по крайней мере с массивами.

При использовании for…in с объектами желательно делать проверку на наличие свойства как показано ниже что бы исключить из результата неожиданные объекты наследованные от прототипа.

for(name in myArray){
    if(object.hasOwnProperty(name)){
        //Do something
    }
}

Вывод: Используйте цикл for для массивов и for…in с проверкой методом hasOwnProperty() для объектов.

Правильно используйте скобки

Нижеследующий код работает неправильно.

При клике мышкой на любую ссылку Вы получите «You’ve clicked 10»

var unorderedList = $("ul");
for(var i =0; i < 10; i++){
    $(""), {
        id: i,
        text: "Link " + i,
        click: function() {
        console.log("You've clicked " + i);
        }
    }).appendTo(unorderedList);
}

Исправленный код.

var unorderedList = $("ul"), i;
for(i = 0; i < 10; i++){
    $(""), {
        id: i,
        text: "Link " + i,
        click: function(index) {
            return function(){
                console.log("You've clicked " + index);
            }
        }
    }).appendTo(unorderedList);
}

Область видимости

Область видимости определяется не фигурными скобками а функцией.

Рассмотрим нижеследующий код.

function eat() {
    var food = "bacon",
    isHungry = true;
    if (isHungry) {
        var timeToWait = 10;
        alert("Waiting " + timeToWait + " minutes");
        chew();
    }
    function chew() {
        var bodyPart = "mouth";
    }
    alert("After waiting " + timeToWait + " minutes I am eating " +
    food + " with my " + bodyPart);
}

Разработчик C# посмотрев на код, представленный выше, скорее всего подумает, что переменная timeToWait доступна только в рамках выражения if (isHungry){}, но это не так.

Переменная timeToWait доступна везде внутри функции eat() что значит, что и функция chew() тоже имеет доступ к переменной.

Вывод: Определяйте все свои переменные в начале функции, так чтобы область видимости была очевидна.

Вы можете получить проблемы если в Вашем коде встретится похожая конструкция.

Подъем переменных

В JavaScript все переменные внутри функции находятся в одном поле видимости. Внутри функции JavaScript сначала ищет все переменные и поднимает их объявления в начало функции и инициализирует как undefined.

Присвоение Ваших значений остается там, где Вы это сделали в коде.

Это называется про поднятие переменных (variable hoisting).

Рассмотрим пример.

function sayHello(firstName, middleName, lastName) {
    var fullName = firstName + " " + middleName + " " + lastName;
    if (fullName === "Carlos Rey Norris") {
        var nickName = "Chuck";
        console.log("Hello " + nickName + " " + fullName.split(" ")[2]);
    }
}
sayHello("Carlos", "Rey", "Norris"); //Hello Chuck Norris

JavaScript «За кадром» берет переменные fullName и nickName и объявляет их в начале функции. Их присвоения остаются на своем месте, но их объявления переносятся.

Получается примерно так.

function sayHello(firstName, middleName, lastName) {
    //Hoists all the variable declarations to the top
    //of the function and sets to undefined
    var fullName = undefined,
    nickName = undefined;
    //fullName assignment remains in original position
    fullName = firstName + " " + middleName + " " + lastName;
    if (fullName === "Carlos Rey Norris") {
        //nickName assigned remains in original position
        nickName = "Chuck";
        console.log("Hello " + nickName + " " + fullName.split(" ")[2]);
    }
}
sayHello("Carlos", "Rey", "Norris"); //Hello Chuck Norris

Незнание этого может привести к ошибкам в таких случаях как следующий.

for(var i = 0; i < 10; ++i){
    for(var i = 0; i < 5; ++i){
        alert("Hello");
    }
}

Это будет бесконечный цикл.

Статья написана на основе статьи How Good C# Habits can Encourage Bad JavaScript Habits by Elijah Manor

Перевод и редакция Григорий Жуков