Author Archives: bunyk

Конспект Vue.js

Не варто припиняти вчити щось нове, правда? І писати – надійніший метод запам’ятати ніж просто читати, тому спробую повернути блог до життя.

CDN

Найпростіший спосіб яким ви можете почати використовувати Vue – це завантажити його на свою сторінку з CDN: https://cdnjs.cloudflare.com/ajax/libs/vue/2.1.10/vue.min.js

Hello world!

Якщо у нас є такий HTML шаблон:

<div id="app">
  {{title}}
</div>

То мінімальний JavaScript який дозволяє його заповнити виглядає так:

var data = { // Модель - це просто будь-який об'єкт
    title: "Hello world!"
};
new Vue({
  el: "#app", // вибрати елемент за id
  data: data  // приєднати модель
});

Тепер, якщо в консолі браузера написати:

data.title = 'It works!'

То текст на сторінці зміниться автоматично. (І не треба ніякої мороки з дайджест-циклом через angular.element(e).scope().$apply() (Ангуляр-страждання, забийте)).

От так в’ю оновлюється коли змінюється модель. Як користувач може змінити модель?

Двостороннє з’єднання даних

Не змінюючи JavaScript додамо в наш HTML поле для вводу тексту (зверніть увагу на атрибут v-model):

<div id="app">
  <h2>{{title}}</h2>
  <input type="text" v-model="title" />
</div>

Все, тепер при зміні тексту в полі редагування, зміниться і заголовок.

Відображення масивів

Змінимо дані аби вони містили якийсь масив, наприклад з іменами атрибутів об’єкта Vue:

var data = {
    title: "Vue contains:",
    list: Object.keys(Vue)
};

Тепер, аби повторити якийсь елемент HTML для кожного елемента масиву, використовуємо атрибут v-for:

<div id="app">
    <h2>{{title}}</h2>
    <ul>
        <li v-for="key in list">{{key}}</li>
    </ul>
</div>

Оновивши сторінку в браузері побачимо щось таке:

Vue contains:

  • util
  • set
  • delete
  • nextTick

І знову ж таки, при зміні того списку, вміст сторінки оновлюється.

Класи і стилі

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

<div id="app">
    <h2>{{title}}</h2>
    <input type="text" v-model="title">
    <label><input type="checkbox" v-model="edit">Edit</label>
</div>

Модель проста:

var data = {
    title: "Change me",
    edit: false
};

Залишилось приховати редактор коли edit == false і приховати заголовок коли він true.

Використовуємо атрибут v-bind:style, який приймає вираз що повертає об’єкт з властивостями CSS:

    <h2 v-bind:style="{display: edit ? 'none': 'block'}">{{title}}</h2>
    <input type="text" v-model="title"
        v-bind:style="{display: edit ? 'inline': 'none'}"
    />

Якщо ж у нас є клас для якого описаний стиль:

.hidden {
    display: none;
}

То можна використати v-bind:class який задає вираз що повертає об’єкт, ключами якого є назви класів, а булевими значеннями задається чи клас буде застосований до об’єкта. Також можна повернути масив з класами.

    <h2 v-bind:class="{hidden: edit}">{{title}}</h2>
    <input type="text" v-model="title" v-bind:class="{hidden: !edit}" />

Кнопки та інші події

Добре, а що якщо я хочу аби щось робилось при натисненні на кнопку? Наприклад те ж перемикання режиму редагування.

Спочатку треба оновити код створення нашого об’єкта Vue, передавши в його конфігурацію також поле methods. Це об’єкт який містить методи доступні з HTML. Методи мають доступ до моделі через змінну this. Код перемикача режиму редагування тривіальний:

new Vue({
    el: '#app',
    data: data,
    methods: {
        toggleEdit: function() {
            this.edit = !this.edit;
        }
    }
});

Тепер, щоб запускати цей метод, додамо справа від заголовка кнопку. Дія яка відбувається по кліку на елементі задається в атрибуті v-on:click:

<h2 v-bind:class="{hidden: edit}">
        {{title}}
        <button v-on:click="toggleEdit">Edit</button>
    </h2>

Залишається лише проблема того що при перемиканні в режим редагування кнопка пропадає разом із заголовком, бо вона в ньому. Але це й логічно, бо ми вже в режимі редагування, кнопка “Edit” нам не потрібна. Давайте вимкнемо редагування коли користувач натисне “Enter”. Знаєте як це зробити? v-on:keyup.enter

    <input type="text"
           v-model="title"
           v-bind:class="{hidden: !edit}"
           v-on:keyup.enter="toggleEdit"
     />

Ооо, дас іст фантастіш! Хіба ні? Але ще краще, це така частовживана директива, що замість v-on: можна писати @, і v-on:click="toggleEdit", можна написати @click="toggleEdit".

P.S. Нестандартні для HTML атрибути елементів як і в Angular називаються директивами, і я їх надалі так називатиму. У Vue-js починаються з префікса v-.

Обчислювані властивості

Давайте напишемо форму для вводу паролю з валідацією.

<div id="app">
    <input type="password"
           v-model="password"
           placeholder="Choose password"
    />
    <br />
    <input type="password"
           v-model="rePassword"
           placeholder="Repeat your password"
    />
    <p>{{formError}}</p>
</div>

В formError будемо класти повідомлення про те що паролі закороткі або не співпадають. Але як? Є ще одне поле куди можна складати функції, називається computed. Ці функції автоматично переобчислюватимуться коли змінюється модель, а в HTML виглядають як звичайні змінні. Тому валідація може виглядати так:

var data = {
    password: '',
    rePassword: '',
};
new Vue({
    el: '#app',
    data: data,
    computed: {
        formError: function() {
            if(this.password.length < 8) {
                return 'Password should have more than 8 characters';
            }
            if(this.password != this.rePassword)
                return 'Passwords did not match';
            return 'Ok';
        }
    }
});

Питання для самоконтролю

Чув що складати списки питань – ефективніший спосіб вчитися ніж просто читати.

  1. Які параметри приймає Vue і які їх функції?
  2. Що таке v-model і для чого воно?
  3. Що таке v-for і як воно використовується?
  4. Що таке v-bind:style?
  5. Що таке v-bind:class?
  6. Що таке v-bind:class?
  7. Який атрибут задає дію при клацанні по елементу?
  8. Де можна описувати методи які викликаються при різних подіях?
  9. Як викликати метод submitForm при натисненні в елементі клавіші Enter?
  10. Що позначає символ “@”?
  11. Що описують в полі computed</code?

Література

Конспект покриває сторінки 15 по 46 книжки (але тут приклади коду мої, текст теж)

  • Filipova, Olga (2016). Learning Vue.js 2: learn how to build amazing and complex reactive web applications easily with Vue.js. ISBN 978-1-78646-113-1.

Підтримати автора книжки можна тут: https://www.packtpub.com/web-development/learning-vuejs-2


Filed under: Кодерство, Павутина Tagged: JavaScript

Побудова “скриньок з вусами” львівських квартир що здаються на сьогодні

Я ще минулого року помітив що в питаннях про Python на StackOverflow обговорюють якісь панди. Це, як виявилось обгортка навколо matplotlib, numpy і подібних гарних речей. А ще, лазячи по своїх документах в Google знайшов скачану вже позаминулого року стіну групи пошуку нерухомості вконтакті. І так співпало що я і мій колега-аналітик зараз шукаємо квартиру у Львові. Я йому показав цей файл, і він загорівся бажанням проаналізувати ще якийсь сайт оголошень.

При всій повазі до lun.ua, але тут я прорекламую dom.ria.com. Передовсім, там є українська версія. А ще, можливість скачати результати пошуку як електронну таблицю, хоч і в xls форматі, і лише одну сторінку.

В python читати xls вміє бібліотека xlrd, тому треба доставити ще й її. Pandas взагалі має багато необов’язкових залежностей:

sudo pip3.5 install jupyter pandas xlrd matplotlib
jupyter notebook # дуже модний графічний інтерпретатор

Якщо все поставити як вище і запустити “jupyter”, то можна робити обчислення в отакому документі: https://github.com/bunyk/mypandas/blob/master/dom.ria/dom.ria.ipynb

І можна побудувати графік скринька з вусами:


От, недаремно я деякі лекції з АнДану все таки не проспав! Хоча, який висновок робити з цього графіка – не знаю. Знаю лише що половина квартир потрапляють всередину прямокутника.

А ось гістограми по цінах для однокімнатних і двокімнатних:

Однокімнатні

Однокімнатні

Двокімнатні

Двокімнатні

Який з цих гістограм робити висновок окрім того що квартир дешевших за 2000 грн (окрім викидів) не буває (а я зараз живу за 700 грн/міс, хоча це пів квартири) – теж не знаю. Може ви самі якийсь зробите. І так, до речі, я шукаю одно чи двокімнатну квартиру десь в другому або третьому квартилі цін в районі вулиці Липинського.


Filed under: Інструменти, Кодерство, Павутина Tagged: графіка, математика, Python

Зміни моделі, події і чистота функцій в Elm

Ця публікація містить ретельно закоментовану альтернативу TodoMVC на Elm. Правда щоб зрозуміти все одно спершу варто прочитати приклади Elm на вікіпедії і основи архітектури Elm програм (вона подібна до Redux якщо ви знаєте що це слово означає (бо я не знаю)).


Filed under: Кодерство, Павутина Tagged: elm, JavaScript

Упс, я зламав сайт ЦРУ

Точніше вони самі здається дев конфіг на прод залили. Але таке зі мною трапляється рідше ніж я переглядаю фільми про Джейсона Борна, тому зробив скріншот на пам’ять:

Selection_066


Filed under: Нещоденник, Павутина

Javascript, replace *&^%$#@!

Ви знаєте як замінити в тексті одну послідовність символів іншою? Наприклад всі прогалики – на символ підкреслювання. Метод replace()? Вгадали:

> 'this is a test'.replace(' ', '_')
'this_is a test'

Ой, а чого воно лише одну заміну зробило? Бо таке воно ліниве падло. Хочете глобальної заміни – передайте шаблон регулярного виразу який співставляється глобально:

> 'this is a test'.replace(/ /g, '_')
'this_is_a_test'

Працює! А тепер уявімо що нам треба замінити наприклад не прогалик а трубу:

> 'a|b|c|d'.replace(/|/g, '_')
'_a_|_b_|_c_|_d_'

Ну так, треба не забувати що деякі символи в регулярних виразах мають спеціальне значення і їх треба екранувати:

> 'a|b|c|d'.replace(/|/g, '_')
'a_b_c_d'

А якщо раптом ви не хочете нічого знати про ці регулярні вирази (або ваш користувач не хоче), то є таки спосіб глобальної заміни підрядка. Знаєте який? StackOverflow підкаже:

> 'a|b|c|d'.split('|').join('_')
'a_b_c_d'

І я не знаю що на це сказати. Піду краще посплю.


Filed under: Кодерство Tagged: JavaScript

Проста схема перетворення інтерактивної процедурної програми з goto в функціональну рекурсивну

Власне вся передмова помістилась в заголовок. Хоча може для цього “паттерну” є коротша назва.

Існує клас інтерактивних програм які очікують вводу користувача, потім залежно від того вводу щось роблять, потім знову очікують вводу і так далі. Наприклад якась така програма “вгадай число”:

import random

def game():
    print('Як тебе звуть?')
    user = input()

    print('Привіт,', user)
    print('Давай пограємо гру відгадай число?')

    while True:
        number = random.randint(1, 10)
        print('Я загадав число від 1 до 10.')
        print('Спробуєш вгадати?')

        while True:
            print('Вводь свій варіант:')
            guess = input()
            if guess == number:
                print('Ого, так швидко вгадав. Грати ще раз?')
                while True:
                    answer = input()
                    if answer in ('так', 'ні'):
                        break
                    print('Ну то так чи ні?');

                if (answer == 'ні'):
                    print('Ну тоді бувай!')
                    return
                if (answer == 'так'):
                    break
            else:
                print('Ні, моє число ' +
                    ('більше' if (scope.guess < scope.number) else 'менше')
                    + ', пробуй ще'
                )
game()

JavaScript має з такими програмами проблему, бо він ніколи не зупиняється очікуючи на ввід користувача (якщо не рахувати функції alert() та компанії, він викликає функції які обробляють події, як от подію вводу. Щоб написати подібну програму для браузера, ми повинні реалізувати в ньому щось на зразок “консолі”:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
</head>
<body>
    <pre id="console"></pre>
    <input type="text" id="input" />

<script>
(function() {
    var input = document.getElementById('input');
    var output = document.getElementById('console');

    var write = function(text) {
        // Write text to console
        output.innerHTML += text;
    };
    var writeln = function(text) {
        // Write line to console
        write(text + 'n');
    };
    
    // Register default callback of echoing input
    var input_callbacks = [function(input) {
        writeln('> ' + input);
    }];

    var on_input = function(callback) {
        // Register input callback

        input_callbacks[1] = callback;
    }

    input.onkeyup = function(e) {
        // Call all callbacks on input
        if (e.keyCode == 13) { // Enter pressed
            for(var i=0; i < input_callbacks.length; i++) {
                input_callbacks[i](input.value);
            }
            input.value = '';
        }
    };

    // Module exports:
    window.CLI = {
        write: write,
        writeln: writeln,
        on_input: on_input
    };
}());
</script>

<script>
// Ну а тут буде наша програма для "консолі". 
</script>
</body>
</html>

Ми маємо модуль CLI, який містить три функції. write та writeln дописують до елемента “консоль” переданий їм текст, а функція передана в on_input, буде викликатись отримуючи вміст елемента input коли в ньому натиснуть Enter. Проблема якраз в тому що буде викликатись окрема функція, якій передадуть ввід, і нема функції яка б той ввід просто повернула. Як написати для такої консолі інтерактивну гру “вгадай число”?

Тут на допомогу може прийти цікаве визначення алгоритму, яке я знайшов в книжці “Мистецтво програмування” Дональда Кнута (ні, я її не осилив, не зміг запам’ятати набір інструкцій MIX, хоча може варто пошукати якийсь емулятор цієї машини, щоб та книжка веселіше проходилась).

Так от, Кнут пише що будь-яку програму можна задати фунцією P(step, scope), яка приймає набір аргументів scope (що може бути одним аргументом – словником). Таким чином ми уникаємо зміни стану і оператора присвоєння, викликаючи функцію рекурсивно з новини аргументами.

Тому наприклад таку програму написану на бейсікоподібному псевдокоді:

    i = 0
1:  writeln i
    i = i + 1
    goto 1

Можна переписати на JavaScript рекурсивно так:

    function exec(step, scope) {
        CLI.writeln(scope.i);
        exec(step, {i: scope.i+1});
    }

    exec(1, {x: 0});

Варто також зауважити що JavaScript не має оптимізації хвостової рекурсії, тому така програма в Firefox зупиняється вивівши число 11085, і пожалівшись в консоль повідомленням “too much recursion”.

Тобто правило таке – як тільки ми хочемо змінити стан – ми викликаємо нашу функцію яка задає програму передавши їй цей новий стан. Ми також можемо робити умовні і безумовні переходи, викликаючи нашу функцію з різними значеннями параметра step. Подивимось як ми зможемо переписати програму гри “вгадай число”, яку описали вище на JavaScript. Але для цього спершу перепишемо її на наш діалект BASIC:

1:  writeln 'Як тебе звуть?'
    input user
2:  writeln 'Привіт,' + user
    writeln 'Давай пограємо гру "Відгадай число"?'
3:  writeln 'Я загадав число від 1 до 10.'
    number = randint(1, 10)
    writeln 'Спробуєш вгадати?'
4:  writeln 'Вводь свій варіант:'
    input guess
5:  if guess == number then 9
6:  if guess < number then 8
7:  writeln 'Ні, моє число менше, пробуй ще' 
    goto 4
8:  writeln 'Ні, моє число більше, пробуй ще'
    goto 4
9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    input answer
10: if anwser == 'так' then 3
11: if answer == 'ні' then 13
12: writeln 'Ну то так чи ні?'
    input answer
    goto 10
13: writeln 'Ну тоді бувай!'

Тепер переписуємо цю програму на JavaScript за такими правилами:

  1. Кожен набір операторів що починається міткою ми записуємо в блок:
    if (step == 1 /*мітка*/) {
        // Код сюди
        return;
    };
    
  2. Кожне присвоєння відбувається в scope:
    if (...) {
        scope.number = Math.floor(Math.random() * 10) + 1;
        ...
        return;
    };
    
  3. Кожен блок що закінчується goto закінчується викликом exec:
    if (...) {
        // Код сюди
        exec(4 /* куди послало goto */, scope);
        return;
    };
    
  4. Кожен блок що закінчується if then:
    if (...) {
        // Код сюди
        if (...) {
            exec(3 /* куди послав then */, scope);
        } else {
            exec(11 /* перехід до наступного блоку */, scope);
        }
        return;
    };
    
  5. Кожен блок що закінчується input закінчується викликом exec для наступного кроку в callback:
    if (...) {
        // код сюди
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    
  6. Програма починається без змінних і з першого кроку:
    exec(1, {});
    

Таким чином з “бейсіка” на JavaScript програму можна переписати так:

function exec(step, scope) {
    // 1:  writeln 'Як тебе звуть?'
    //     input user
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    };
    // 2:  writeln 'Привіт,' + user
    //     writeln 'Давай пограємо гру "Відгадай число"?'
    if (step == 2) {
        CLI.writeln('Привіт, ' + scope.user);
        CLI.writeln('Давай пограємо гру "Відгадай число"?');
        exec(3, scope);
        return;
    };
    // 3:  writeln 'Я загадав число від 1 до 10.'
    //     number = randint(1, 10)
    //     writeln 'Спробуєш вгадати?'
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    };
    // 4:  writeln 'Вводь свій варіант:'
    //     input guess
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    };
    // 5:  if guess == number then 9
    if (step == 5) {
        if (scope.guess == scope.number) {
            exec(9, scope)
        } else {
            exec(6, scope)
        };
        return;
    };

    // 6:  if guess < number then 8
    if (step == 6) {
        if (scope.guess < scope.number) {
            exec(8, scope)
        } else {
            exec(7, scope)
        };

        return;
    };

    // 7:  writeln 'Ні, моє число менше, пробуй ще' 
    //     goto 4
    if (step == 7) {
        CLI.writeln('Ні, моє число менше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 8:  writeln 'Ні, моє число більше, пробуй ще'
    //     goto 4
    if (step == 8) {
        CLI.writeln('Ні, моє число більше, пробуй ще');
        exec(4, scope)
        return;
    };

    // 9:  writeln 'Ого, так швидко вгадав. Грати ще раз?')
    //     input answer
    if (step == 9) {
        CLI.writeln('Ого, так швидко вгадав. Грати ще раз?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 10: if anwser == 'так' then 3
    if (step == 10) {
        if (scope.answer == 'так') {
            exec(3, scope)
        } else {
            exec(11, scope)
        };

        return;
    };

    // 11: if answer == 'ні' then 13
    if (step == 11) {
        if (scope.answer == 'ні') {
            exec(13, scope)
        } else {
            exec(12, scope)
        };
        return;
    };

    // 12: writeln 'Ну то так чи ні?'
    //     input answer
    //     goto 10
    if (step == 12) {
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(10, scope);
        });
        return;
    };

    // 13: writeln 'Ну тоді бувай!'
    if (step == 13) {
        CLI.writeln('Ну тоді бувай!');
        CLI.on_input(function() {}); 
        return;
    };
}

exec(1, {});

Можна звісно коротше, якщо робити умовний оператор без переходів в інший блок:

function exec(step, scope) { 
    console.log(step, scope);
    if (step == 1) {
        CLI.writeln('Як тебе звуть?');
        CLI.on_input(function(input) {
            scope.user = input;
            exec(2, scope);
        });
        return;
    }
    if (step == 2) {
        CLI.writeln('Привіт,' + scope.user);
        CLI.writeln('Давай пограємо гру відгадай число?');
        exec(3, scope);
        return;
    }
    if (step == 3) {
        CLI.writeln('Я загадав число від 1 до 10.');
        scope.number = Math.floor(Math.random() * 10) + 1;
        CLI.writeln('Спробуєш вгадати?');
        exec(4, scope);
        return;
    }
    if (step == 4) {
        CLI.writeln('Вводь свій варіант:');
        CLI.on_input(function(input) {
            scope.guess = input;
            exec(5, scope);
        });
        return;
    }
    if (step == 5) {
        if (scope.guess == scope.number) {
            CLI.writeln('Ого, так швидко вгадав. Грати ще раз?')
            CLI.on_input(function(input) {
                scope.answer = input;
                exec(6, scope);
            });
            return;
        }
        CLI.writeln('Ні, моє число ' +
            (scope.guess < scope.number ? 'більше': 'менше')
            + ', пробуй ще');
        exec(4, scope);
        return;
    }
    if (step == 6) {
        if (scope.answer == 'так') {
            exec(3, scope);
            return;
        }
        if (scope.answer == 'ні') {
            CLI.writeln('Ну тоді бувай!');
            CLI.on_input(function(){});
            return;
        }
        CLI.writeln('Ну то так чи ні?');
        CLI.on_input(function(input) {
            scope.answer = input;
            exec(6, scope);
        });
        return;
    }

};

exec(1, {});

Які з цього висновки? Ми можемо уникнути зайвих вкладень якщо нам треба зробити ланцюзок колбеків – запит, обробка відповіді, новий запит що залежить від результатів, обробка відповіді і т.д. Головне, аби це не був ланцюжок з тисяч колбеків, бо нарвемось на переповнення стеку. Тоді доведеться писати власну оптимізацію хвостової рекурсії.


Filed under: Кодерство Tagged: книжки, JavaScript, розробка, Python

Новий безкоштовий онлайн-курс веб-розробки для початківців

Ну а шо, в мене тепер купа досвіду в викладанні, а попит є. То чому б ні? Першим 10-тьом учасникам – участь безкоштовна. ;) А далі буде видно скільки я осилю. Може раз на тиждень, поки не прийде пора відпусток літом?

Прем’єра в четвер, 17-го березня, онлайн, в Google Hangouts. Приєднатись можна за посиланням: https://plus.google.com/events/cd18to4v9bob2p94ume6fb7qih8

Курс розглядатиме всі аспекти, починаючи від верстання статичних сторінок (що я погано вмію :) ), і контролю версій, до NoSQL баз даних (бо SQL я погано вмію :) ), і того що таке REST API. Ну, коротше кажучи, будемо розбиратись разом. Але почнемо з простого – що таке Git, Github, HTML і як помістити останній на Github pages.

Викладаю я більш-менш терпимо. Ось відгук, там фраза “Окрема подяка за підбір тренерів” – то про мене. ;)

По ходу справи будемо писати підручник: https://uk.wikibooks.org/wiki/MEAN

Картинка для привернення уваги: Git changing head uk

Пропозиції про зміст, час, список літератури та та інші аспекти організації курсу приймаються прямо тут, в коментарях. В нас поки що нема українського віківерситету, але вікіпідручник і його адміністрація в моїй особі – до ваших послуг. :)


Filed under: Кодерство, Нещоденник Tagged: освіта, HTML

Як написати свій букмарклет?

Букмарклет (bookmark) – це слово створене поєднанням слів bookmark (закладка) і applet (application (застосунок) + зменшувальний суфікс -let). Таким чином правильним перекладом bookmarklet було б “закластуночок” (чи “застокладочка”), але ви цього краще не кажіть Юрку Зеленому. Але я відхиляюсь. Отож, букмарклет, це маленька програма що міститься в закладці.

Як її туди помістили? Ну, виявляється браузери крім протоколів http, ftp, ітп, розуміють такий протокол як javascript. І коли отримують посилання з цим протоколом, при кліку по ньому не змінюють сторінку, а виконують його href як код JavaScript. Щоб побачити приклад простого букмарклету, створіть документ bookmarklet.htm, і помістіть в нього такий код:

<html>
<head><meta charset="utf-8" /></head>
<body>
<a href="javascript:
    alert('Привіт!')
">Мій букмарклет</a>
</body>
</html>

Якщо відкрити його в браузері, ви побачите чисту сторінку з єдиним посиланням Мій букмарклет, яке при кліку буде показувати повідомлення “Привіт!”. Якщо перетягнути це посилання на панель закладок в браузері, то тепер ви зможете в будь-який момент отримати привітання на будь-якій сторінці яку дивитесь, досить лише натиснути букмарклет.

Але це не дуже корисний букмарклет. Щоб він робив щось корисніше, досить замінити код в рядку 5, на якийсь корисніший.

Наприклад є такий сайт – lib.rus.ec. Вони хоч мають домен еквадору – але москалі, і коли відкрити їх сайт, то вискакує повідомлення, яке блокує доступ до сторінки, і просить грошей за чужі книжки. Я переконаний що в цьому випадку дозволяється використати принцип “грабуй награбоване”, і додати в букмарклет такий код:

 document.getElementsByClassName('lsp-overlay')[0].remove()

І тоді при кліку по букмарклету це вікно пропадатиме. Клас, правда? (Не забудьте забрати виклик alert() який ми написали там раніше)

Але давайте зробимо ще щось складніше і корисніше. Давайте зробимо букмарклет, який оформлює посилання на веб-сторінку в форматі вікіпедії cite web, і дає її нам в якомусь вікні. Це такий шаблон, який гарно оформлює посилання, але заповнювати його руками – ще та морока.

Для букмарклетів є кілька правил. Їх код все таки міститься всередині атрибуту елемента HTML, і повинен відповідати стандартам цієї мови. Наприклад всі подвійні лапки """ (ну ви зрозуміли), повинні бути заміненими HTML-кодом ". Крім того, перехід на новий рядок вважається еквівалентним пробілу, тому однорядкові коментарі можливі лише в кінці скрипта:

alert('Це вискочить');
// alert('Це не вискочить бо закоментовано');
alert('Це теж не вискочить бо коментар продовжується');

Ну і тому що ввесь скрипт здається браузеру написаним на одному рядку – бажано шанувати крапки з комою.

Якщо ми хочемо написати великий букмарклет, який можливо використовуватиме jQuery, та інші бібліотеки, нам захочеться їх якось підвантажувати. Але, віднедавна в адміністраторів серверів з’явилась нова можливість збільшувати безпеку вводячи обмеження на джерела з яких на сайт завантажуються ресурси – Content Security Policy, тому такі букмарклети можуть працювати не всюди. Можливо доведеться все переписати, мініфікувати і запакувати, але на всяк випадок, ось вам код підвантаження скрипта:

(function () {
    var script = document.createElement('script');
    script.src='https://gist.githubusercontent.com/bunyk/6ae97e5c3de490cfb4a1/raw/b7fcfde5c40314262893761418a6a25dd6ed0ce8/cite_web.js';
    document.body.appendChild(script);
}());

Подібним чином вже з того скрипта ми можемо підвантажити jQuery та наприклад CSS.

Я тут наковбасив трохи JavaScript, який можливо не на всіх сайтах працює, і не всі дані статті витягує, але на моєму блозі він дає щось подібне до цього:

cite_web

Щоб поставити собі такий, перенесіть оце посилання в закладки: “{{cite web|…“, а тоді в контекстному меню закладки натисніть “Властивості”, чи “Редагувати”, і замініть текст на

(function () {
    var script = document.createElement('script');
    script.src='https://gist.githubusercontent.com/bunyk/6ae97e5c3de490cfb4a1/raw/3102c3e285c6e24a9aed98d1bc7f0b7abeda20fc/cite_web.js';
    document.body.appendChild(script);
}());

Ось код:


Filed under: Кодерство, Павутина Tagged: Лайфхаки, вікіпедія, JavaScript

Логуючий фільтр для Angular expression

Вирази в Angular (те що в фігурних дужках і ngBind) – не зовсім те що Javascript. Так каже документація.

Context: JavaScript expressions are evaluated against the global window. In Angular, expressions are evaluated against a scope object.

А тому ми не можемо там використати console.log. Але можемо написати наприклад такі фільтри:

    angular
        .module('starter')
        .filter('log', function() {
            // do not change value, but log it in console
            return function(input) {
                console.log(input);
                return input;
            };
        })
        .filter('justlog', function() {
            // do not render value but log it in console
            return function(input) {
                console.log(input);
                return '';
            };
        });

Хоча напевне існує якийсь правильніший, вже вбудований спосіб зневадження в Angular. :)


Filed under: Кодерство, Павутина Tagged: Angular, JavaScript

Вступ в AngularJS (1)

Я почав писати цей конспект від початку 2015-го, за цей час вийшов AngularJS 2, напевне варто переписати. Хоча другий Angular написаний на TypeScript, тому він напевне вже не JS. Або вивчити React і написати конспект для нього. Вибір безмежний. :)

Написано на основі шикарного курсу від Code School. Просто щоб не передивлятись кожного разу перед тим як знову засвербить щось написати для фронт-енду.

Angular.js дозволяє писати шаблони прямо в HTML, розширюючи його своїми директивами, а потім автоматично заповнюючи даними.

Директива – це маркер (атрибут тегу) чи тег HTML що каже Angular-у запустити якийсь JavaScript код.

Єдиний файл що потрібен для початку роботи – angular.js.

Модулі

Код в Angular.js поміщається в модулі. Модулі містять код і описують залежності.

Щоб створити модуль пишемо:

var app = angular.module('store', []);

Перший аргумент – назва модуля, другий – список залежностей (якщо без залежностей – то порожній).

Тоді в html пишемо:

<html ng-app="store">

І це запустить наш модуль коли документ завантажиться.

І хоча наш модуль нічого не робить, але його включення вже допомагає розглядати код html який ми пишемо як директиви Angular.

Після чого можна писати на сторінці вирази. Як от

<p>2 + 2 = {{2 + 2}}</p>

Контролери

Щоб передати дані з JS в HTML, потрібно використати контролер:

var gem = {
    name: 'Dodecahedron',
    price: 10,
};

app.controller('StoreController', function() {
    this.product = gem;
});

Важливо щоб контролери називались КемелКейсом і останнім словом завжди було Controller.

Далі, в html, пишемо

<div ng-controller="StoreController as store">
<h1>{{store.product.name}}</h1>
<h2>{{store.product.price}}</h2>
</div>

store – аліас який ми описали і що використовується всередині виразів. Доступний лише всередині елементу для якого описаний контролер.

Директиви

ng-show – покаже елемент лише коли значення виразу істинне.

<div ng-controller="StoreController as store">
<button ng-show="store.product.canPurchase">Add to Cart</button>
</div>

ng-hide – навпаки, сховає елемент коли значення істинне.

ng-repeat – повторить елемент ітеруючи по якомусь списку.

ng-click – виконає код написаний всередині значення директиви при кліку по ньому.
ng-init – виконає код написаний всередині значення директиви при відображенні сторінки. Не користуйтесь ним, покладіть код який хочете покласти туди, всередину контролера.

<div ng-controller="StoreController as store">
<div ng-repeat="product in store.products">
<button ng-show="product.canPurchase">Add to Cart</button>
</div>
</div>

ng-src – директива що встановлює значення атрибуту src в тегу на зразок img. Тому що встановлення його значення через <img src="{{value}}">, призведе, перед тим як спрацює Angular, до спроби браузера завантажити файл “{{value}}“, і помилки 404. А цього ми не хочемо.

ng-class="{ 'class1': condition1, class2: condition2 }" – дозволяє додавати і забирати класи з елемента залежно від значення виразів умов.

Фільтри

При виводі даних в шаблонах, їх можна пропускати через фільтри. Загальний синтаксис:

<p>2 + 2 = {{вираз|фільтр:опції}}</p>

<!-- або так: -->
<li ng-repeat="product in store.products | limitTo:3 | orderBy:'-price'">

Форми

Директива ng-model прив’язує значення елемента форми до моделі (атрибута контролера). Вони оновлюються автоматично.

Щоб якийсь код виконувався коли форма відправляється, ми додаємо цей код в значення директиви ng-submit. І дописуємо директиву novalidate, щоб вимкнути стандартну валідацію HTML.

<form name="reviewForm"
      ng-controller="ReviewController as reviewCtrl"
      ng-submit="reviewForm.$valid && reviewCtrl.addReview(product)" 
      novalidate
>

Angular автоматично додає до полів класи – ng-pristine – до поля до якого ще не додавали код, ng-dirty – до зміненого поля, і ng-invalid – до поля що не проходить валідацію, та ng-valid – до того що проходить.

Поля валідуються по атрибуту type. Може бути наприклад email, url, number (додатково є атрибути min, max).

Далі в курсі (ось слайди) ще йдеться про те як

  • включати розмітку з інших файлів
  • писати власні директиви і власні html елементи
  • завантажувати дані через XHR

Щоправда, цього не треба щоб написати застосунок який я хочу написати. Але Про нього пізніше, бо github лежить, і вже трохи пізно. :)


Filed under: Кодерство, Павутина Tagged: JavaScript