Разработка, HowTo

Печать в Chrome без подтверждения

Снимок экрана от 2017-10-28 01-07-21Существует задача для pos-систем печатать что-либо автоматически: чеки, квитанции и т.п. Хочется, чтобы печать можно было инициировать из скриптов, но по дефолту все браузеры показывают диалог печати или предварительный просмотр.

Это неудобно при создании киосков.

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

Что нам предлагают:

  • Использовать опцию —disable-print-preview (или включать аналогичную настройку в about:flags)
  • Вместе с ней включать опцию —kiosk-printing, которая в теории должна избавить нас от диалога выбора принтера.

А вот и нет. Эти опции нельзя использовать совместно. В чем суть: —disable-print-preview передает управление на диалог выбора принтера в системе, а это лишает нас контроля над выбором пользователя. Во многих (во всех) конфигурациях windows и linux нельзя избавиться от шага выбора принтера, а предварительный просмотр в хроме подменяет диалог выбора принтера.

Опция —kiosk-printing воздействует только на диалог предварительного просмотра chrome сразу печатая просматриваемый документ, а если мы его отключим, то и смысла в установке этого параметра нет.

Отсюда вывод: если вы хотите делать автоматическую печать, то используйте только —kiosk-printing.

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

<!DOCTYPE html>
<html>
<head>
<title>autoprint</title>
</head>
<body>
data
<script>
    window.addEventListener('load', function() {
      window.print()
    })
  </script>
</body>
</html>

И это сработает — принтер начнет печатать.

UPD:

Chromium, issue 169004: баг с окном предварительного просмотра в режиме kiosk-printing (непофикшено)

Реклама
Обработка изображений, Разработка

JavaScript: копируем img в canvas

Существует несколько способов скопировать имеющееся на странице изображение (img) в canvas. Конечно все они сводятся к простейшему drawImage, но каждый из этих методов применим в своем контексте.

Первый и самый очевидный (а еще универсальный) — загрузить изображение при помощи объекта Image. Он работает только в случае, когда у img есть атрибут src.

var img = new Image();
var srcImg = document.getElementById('src-img');
img.onload = function() {
    // создаем (или получаем) канву
    var backCanvas = document.getElementById('dest-canvas');
    backCanvas.width = this.width;
    backCanvas.height = this.height;
    var backCtx = backCanvas.getContext('2d');

    // рисуем
    backCtx.drawImage(this, 0,0);
};
img.setAttribute('src', srcImg.src);

Недостаток — еще один запрос в сеть.

Второй способ — менее универсален. Картинка у нас уже есть — почему ее нельзя нарисовать на канве?

var backCanvas = document.getElementById('dest-canvas');
var srcImg = document.getElementById('src-img');
backCanvas.width = srcImg.naturalWidth;
backCanvas.height = srcImg.naturalHeight;

var backCtx = backCanvas.getContext('2d');

// рисуем картинку
backCtx.drawImage(srcImg, 0,0);

И вот тут мы используем naturalWidth/naturalHeight. Зачем?

Все очень просто. Если в первом случае width и height означали размеры картинки, то во втором — это будут физические размеры элемента img на странице. И если картинка внутри него будет отмасштабирована, то мы мы скопируем только часть картинки размером width на height.

Как на иллюстрации ниже (картинка там имеет разрешение в десять раз больше нежели физические размеры img и масштабируется).

Выделение_001

А свойства naturalWidth/naturalHeight не поддерживаются в ряде браузеров. поэтому способ универсальным назвать нельзя.

Разработка

AngularJS: сервисы, фабрики, провайдеры

Чтоже это за странные service, factory и provider. Казалось бы — выполняют почти одну и туже функцию, подключаются через DI. Зачем их так много?

На абстрактный пример можно глянуть тут (к слову, автор привел очень хороший пример).

Но никто не рассказывает, зачем оно в реальной практике нужно.

Сервис

Он чаще всего используется для создания разделяемого ресурса.

var constructor = function constructor() {
    this.foo = 'foo'
    this.bar = function bar() {
        
    }
}

.service('someService', constructor)

Когда сервис создается первый раз, то выполняется

var someServiceImpl = new constructor()

И в дальнейшем при подключении зависимостей через DI в объекты будет всегда передаваться someServiceImpl.

Фабрика
это более продвинутая конструкция. Она не является разделяемым ресурсом сама по себе. При каждом использовании фабрики через DI ее значение — это результат вызова фабричной функции.

var factoryFn = function () {
    var someClass = function() {}
    
    return someClass;
}

.factory('someFactory', factoryFn)

При первом инстанцировании фабрики будет вызвана функция factoryFn и ее результат — это уже и есть разделяемое между всеми участниками системы значение.

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

.controller('someController', ['someFactory', function(someFactory) {
    // Тут мы успешно инстанцируем новый объект someClass
    var someClassImpl = new someFactory();
}])

Провайдер
Это тоже фабрика. Но более продвинутаця. Так же точно при первом инстанцировании провайдера выполняется некая функция и ее результат становится разделяемым значением.

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

Как пример — библиотека для oauth-авторизации. на этапе конфигурирования в нее передаются токены.

var providerFn = function() {
    var someModel = function() {
        // это модель
    }
    
    this.configure = function() {
        // делаем какую-то конфигурацию
    }
    
    this.$get = function() {
        return new someModel()
    }
}

.provider('someProvider', providerFn)

И теперь самое интересное: при подключении провайдера через DI в качестве разделяемого объекта будет выступать то, что вернула функция $get.

В то же время на этапе конфигурирования нам доступен сам инстанс providerFn и мы можем использовать его функционал по конфигурированияю

.config(['someProvider', function(someProvider) {
    //...
    someProvider.configure(options)
}])
Разработка

JavaScript и области видимости

Об этой особенности полезно иногда вспоминать.

// глобальная переменная, которую мы попытаемся дальше получить
var bar = 42;

// Мозг предполагает, что до объявления bar в теле функциии глобальный bar будет доступен
function simple_define() {
    alert(bar);
    var bar = 10;
    alert(bar);
}

// Но это не так. Функция выше на самом деле выглядит вот так
function real_define() {
    var bar;
    alert(bar);
    bar = 10;
    alert(bar);
}

// А что же с условиями? Разве они не создают локальные области видимости?
function define_inside_if() {
    if (true) {
        var bar = -10;
    }
    
    alert(bar);
}

// Ну а циклы?
function define_inside_while() {
    do {
        var bar = 10;
    } while (false);
    alert(bar);
}

// И переменные из for?
function define_inside_for() {
    for (var bar = 0; bar < 10; bar++);
    alert(bar);
}

/* 
 * Да. Это особенность js - компилятор собирает все объявления переменных текущей области видимости
 * и выделяет под них все требуемые ресурсы сразу при входе в функцию.
 */
Разработка, HowTo

Firefox: NS_ERROR_FILE_CORRUPTED

Столкнулся сегодня с этой ошибкой в консоли фокса.

Причина проста: повреждение хранилища localStorage. Подробнее можно посмотреть официальную документацию.

Решается удалением файла

webappsstore.sqlite

.

В коде же это исключение так же следует обрабатывать корректно (чтобы не пугать пользователя).

try {
    setLocalStorageItem(key, value);
} catch(e) {
    if(e.name == "NS_ERROR_FILE_CORRUPTED") {
        showMessageSomehow("Sorry, it looks like your browser storage has been corrupted. Please clear your storage by going to Tools -> Clear Recent History -> Cookies and set time range to 'Everything'. This will remove the corrupted browser storage across all sites.");
    }
}

Странно, но в моем случае чистка всего-всего таки не помогла. Помогло ручное удаление файла.

Разработка

window.location — не всегда оно работает как надо

Если раньше мы долго ругались на проблемы IE, то сейчас на FF и Chrome — ничего особо не поменялось.

На этот раз отличились механизмы работы с хешом в window.location.

Кейс: мы хотим перенаправить пользователя с помощью js на другую страницу.

Что может пойти не так?

Например у нас есть ссылка вида /foo. И логично предположить, что в современном мире мы хотим сохранить хеш (это нужно для angularjs например) чтобы на новой странице отработал нужный функционал.

Что мы делаем

window.location.pathname = '/foo'

Работает.

Но стоит только в ссылку нечаянно попасть хешу #, как поведение браузеров сразу резко меняется.

window.location.pathname='/foo#bar'

Chrome среагирует правильно и отправит нас по ссылке /foo%23bar, а Firefox — нет. У него будет ссылка /foo#bar.

Ниже иллюстрации.

FF: window.location.pathname bugChrome: window.location.pathnameКак видим FF с задачей не справился. И не перекодировал # автоматом.

Баг этот давний — аж 2009го года.

Поэтому внимательно следите за редиректами если у вас есть спецсимволы в урле.

Проще говоря: pathname в FF вседет себя так же, как и href (за мелким исключением вроде сохранения хеша).

Исходники на посмотреть тут.

Разработка

Ajax и заголовок location: особенности

Все как обычно: в коде ajax-приложения нужно обрабатывать хидер location, который отдает сервер в некоторых случаях.

Это может потребоваться где угодно. И сейчас для нас важен результат (чтобы оно работало).

Как мы все прекрасно знаем хидеры в jquery можно получить через метод getResponseHeader объекта jqXHR (для других библиотек и голого js можно заглянуть в мануалы).

Казалось бы:

$.get('some_url', function(data, status, request) {
console.log(request.getResponseHeader('location'))
})

Но не все так просто.

Если мы отдаем хидер стандартно для php.

header('location: to_url');

То видим. А что видим? А видим, что браузер взял на себя переход по редиректу. Почему? Потому что ответы с кодом 301 и 302 прозрачно обрабатываются самим браузером и в ответе придет уже конечная страница.

Но как только ответ сервера будет 200, так сразу все становится хорошо. Этот код заставляет браузеры забыть об обработке хидера location и позволяет прочитать его на стороне js.

header('location: to_url', true, 200);

В примере можно наглядно посмотреть, как происходит обработка. Достаточно его запустить внутри встроенного сервера php.

Дружим location и js