API Map Yandex: Изменение содержимого метки «на лету»

Задача: разместить внутри метки поле ввода текста (комментарий) и сохранять его значение

Решение:

При формировании Json данных FeatureCollection, внутри каждой метки, в настройке balloonContentBody разместим теги <textarea style=’width: 100%;’></textarea>. И кнопку сохранения результата. Но! Сохранить результат в БД не сложно. Сложно добиться того, чтобы то что пользователь ввел в поле, сохранилось, после перевыбора метки мышкой. Содержимое поля скидывается каждый раз к изначальному, т.к. движек яндекса каждый раз перерисовывает отображение html на основании данных которые у него » в душе». Поэтому функция сохранения будет выглядеть как-то так:

function SaveComment(ls_id){
    console.log("-сохраняем комментарий "+ls_id);
    comment=$("#comment_"+ls_id).val();
    $.post("?r=receipts2/savecomment", {  
            ls_id:ls_id,
            comment:comment
    }).done(function(data) {                        
        console.log(data);
        balloonContentBody=objectManager.objects.getById(ls_id).properties.balloonContentBody;
        start_=balloonContentBody.slice(0,balloonContentBody.search("%;'")+4);
        end_=balloonContentBody.slice(balloonContentBody.search("</textarea>"),balloonContentBody.length);
        balloonContentBody=start_+comment+end_;
        objectManager.objects.setObjectProperties(ls_id,{balloonContentBody:balloonContentBody});
    });                      
}

Что тут происходит?

  1. Изменения сохраняются в базу данных при помощи вызова скрипта receipts2/savecomment
  2. Читаем данные которые яндекс хранит у себя в нутри
  3. Изменем эти данные, добавив комментарий
  4. Сохраняем данные «внутри»

В результате после перещелкивания по метке, комментарий сохраняется.

JavaScript: улучшаем понимание работы функций

Классически функция объявляется используется примерно так:

function Inc(s,n){
  return s+n;
};

Однако её можно объявить и так:

const Inc=(s,n)=>{
  return s+n; 
}

Кроме того! Можно еще короче, используя не явный return:

const Inc=(s,n)=>s+n;

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

const People=(name,soname,age,comment)=>({
  name:name,
  soname:soname,
  age:age,
  comment:comment
});

aa=People("Вася","Сидоров",18,"Вася обычный школьник")

Или например добавить событие onClick к кнопке на странице при помощи стрелочных функций:

but=document.getElementById('#button1');
but.onclick=()=>{
  console.log("Вася нажал кнопку");
}

Выглядит гораздо проще, чем если бы мы использовали «классическую» запись:

function VasyaPress(){
   console.log("Вася нажал кнопку");
};
but=document.getElementById('#button1');
but.onclick=VasyaPress();

Но! только если код внутри функции не сильно длинный. Иначе всё превращается в кашу.

Javascript: удаление элемента массива

В JavaScript как мне кажется не очень удобная реализация работы с массива. Наверное слишком низкоуровневая по сравнению с другими интерпретируемыми языками программирования. Возможно это даёт большую гибкость, но лично мне не удобно, что для простое удаление элемента массива по виду:

mass=[1,2,3,4];
delete mass[2];

Приводит на самом деле не к удалению элемента массива, а пометке, что элемент пустой. Многие пользуются командой splice:

mass=[1,2,3,4];
mass.splice(2,1)

Или даже filter:

brisks_mass=[1,2,3,4];
brisks_mass=brisks_mass.filter((number)=>number!==3) 

Но как говорится «Мне не зашло» (c), ибо есть с чем сравнивать удобство (Python, PHP и т.д.)

JavaScript: декодирование строки в Base64

Вообще штатно есть функции для кодирования/декодирования: atob / btoa. Но один нюанс: если строчку вида «Hello world!» они кодирую-декодируют легко, то с бинарными файлами выйдет ой. Не, они конечно тоже что-то сделают, но с «тихой ошибкой» — т.е. результат будет, но не верен. Попался так, когда с сервера передавал файл в base64 на клиент javascript, декодировал его примерно так:

let a = document.createElement("a");
let file = new Blob([atob(data.result)], {type: 'application/xlsx'});
a.href = URL.createObjectURL(file);
a.download = "template_0.xlsx";
a.click();

А в результате, с виду вроде бы валидный файл, экселем открываться отказался. А всё потому, что atob не работает со строками в Uicode. Потому обычно используют следующие обертки для кодирования-декодирования:

function base64ToBytes(base64) {
  const binString = atob(base64);
  return Uint8Array.from(binString, (m) => m.codePointAt(0));
}
function bytesToBase64(bytes) {
  const binString = String.fromCodePoint(...bytes);
  return btoa(binString);
}

JavaScript: Еще один вариант загрузки файла по клику на кнопку

Общая идея следующая: по клику на кнопку вызываем AJAX запрос на сервер с определенными параметрами передаваемыми в POST, затем получив в результат запроса файл — формируем в DOM на файл в формате blob, и тут-же её нажимаем. В результате браузер показывает диалоговое окно сохранения файла.

Таким необычным способом мы убиваем несколько зайцев сразу:

  1. Получаем возможность показать ошибку, если вдруг файл на сервере сформировать не удалось. Это полезно если например файл формируется на сервере «на лету» — например файл XLSX с отчётом
  2. Мы можем передать серверу какие-то условия для формирования файла в POST запросе
  3. На странице мы не размещаем заранее данные в тегах <form></form>, как практикуется в подобных решениях
  4. Пользователю достаточно нажать на кнопку один раз для получения результата.

В результате скрипт формирования файла может выглядеть примерно следующим образом:

На клиенте:

/**
 * Формирование отчета
 * @param {type} filename - имя файла на сервере для формирования отчета
 * @param {type} ext - выходное расширение (например xlsx,csv)
 * @returns {Number}
 */
function get_report(filename,ext){
        $("#global").addClass("loading");    
        var xhr = new XMLHttpRequest();
        xhr.open('POST', '?r=reports/'+filename, true);
        xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
        xhr.responseType = 'blob';     
        xhr.onload = function(e) {
            $("#global").removeClass("loading");    
            if (this.status == 200) {
                var link=document.createElement('a');
                link.href=window.URL.createObjectURL(this.response);                
                console.log("Расширение:"+ext);
                link.download="report."+ext;
                link.click();
            }
            else {
                console.log(e);
                ToastMessage("error","Произошла ошибка при формировании файла. Попробуйте позднее или обратитесь к администратору системы.");
            }
        };     
        var form_data = new Map();
        form_data.period_from = period_from.value;
        form_data.period_to = period_to.value;
        form_data.area = area_select.value;
        form_data.division_check=division_check.checked;
        form_data.period_check=period_check.checked;
        xhr.send(mapToQueryString(form_data));                                          
};

На сервере:

    public function actionGet_report_by_settlers(){               
       $request = Yii::$app->request;      
       $area= $request->post("area");
       $dir=Yii::$app->basePath."/web/templates";
        
      $oSpreadsheet = IOFactory::load($dir . "/report_1_1.xlsx");
      ...
      формируем файл эксель
      ...
      $oWriter = IOFactory::createWriter($oSpreadsheet, 'Xlsx');
      $oWriter->save('php://output');      
    };
1 2 3 11