HTML5 Drag and Drop功能DataTransfer物件format參數注意事項

Drag and Drop(DND)是HTML5裡一個趣味與實用性很高的一個功能,在實作時發現一個有趣的小問題。我們從程式碼來看問題比較好說明。

以下為http://msdn.microsoft.com/en-us/library/ie/ms536744(v=vs.85).aspx範例程式碼:

var sImageURL;
function InitiateDrag() 
/*  The setData parameters tell the source object
   to transfer data as a URL and provide the path.  */
{   
    event.dataTransfer.setData("URL", oImage.src);
}
function FinishDrag()
/*  The parameter passed to getData tells the target
    object what data format to expect.  */
{
    sImageURL = event.dataTransfer.getData("URL")
    oTarget.innerText = sImageURL;
}

以下為http://www.w3schools.com/html/html5_draganddrop.asp範例程式碼:

function allowDrop(ev)
{
ev.preventDefault();
}

function drag(ev)
{
ev.dataTransfer.setData("Text",ev.target.id);
}

function drop(ev)
{
ev.preventDefault();
var data=ev.dataTransfer.getData("Text");
ev.target.appendChild(document.getElementById(data));
}

問題點於在setData方法的format參數,完整是setData(format,data)。以上程式能正常執行,我們再看其他程式。

Knockout.js與MVVM基礎入門補充教材

Web API與MVVM

小弟有幸,被看得起,被找去寫書。所以未來幾個月,應該會有人生第一本電腦技術書藉在書局裡面供大家挑選,最大的願望當然是人手一本,你可以拿去當枕頭,然後睡到落枕…再起來寫程式,也可以拿去包豬肉,再拿來生火烤肉。

在寫書期間,發現,目前中文方面討論MVVM模式的資料實在太少,但要把全部MVVM模式相關寫進去書裡又離題太遠,書裡有一章是使用Knockout.js實作MVVM模式,索性就把官方那非常高互動的教材給翻譯+中文化,本想放入書中當補充教材。但實在想了很久,還是決定使用Blog加電子書方式來公開。Blog你可以線上看,電子書(PDF)可以在平板上看。

Knockoutjs基礎入門教材

如果你想更快速學習Knockout.js與MVVM模式,書會有圖解,會讓你看的一清二楚,不過想快速入門建議可以連上"http://learn.knockoutjs.com/"官方教材,配合本中文化不用多少時間,就可以清楚整體輪廓,打下不少基礎。

基礎打好,未來練功才不會內傷,這是最後決定放出來的原因。希望大家未來的功能練的順利。

下載:Knockoutjs基礎入門教材

Blog全文連結

  1. Knockout教學1 - Knockout.js與MVVM模式簡介
  2. Knockout.js教學2 - 清單與集合
  3. Knockout教學3 - Single Page Applications, SPA
  4. Knockout教學4 - 自訂繫結
  5. Knockout教學5 - 由伺服器載入與儲存資料

Knockout教學5 - 由伺服器載入與儲存資料

伺服器載入與儲存資料

現在,你已經瞭解MVVM模式如何為動態UI有組織地整理用戶端程式碼,以及knockout.js的可觀察物件(observables)、繫結(bindings)、相依追蹤(dependency tracking)是如何運作。

幾乎所有網站應用程式,都需要由伺服器取得資料和傳送修改的資料回去伺服器。因為Knockout.js 是一個純用戶端函式庫,它能非常有彈性運作在任何伺服器端技術(例如,ASP.NET、ASP.NET MVC、Rails、PHP…等),以及任何架構模式、資料庫…等。只要伺服器端程式碼可以傳送與接收JSON資料,就能呈現以下教學。

範例:工作清單

ViewModel:

function Task(data) {
    this.title = ko.observable(data.title);
    this.isDone = ko.observable(data.isDone);
}

function TaskListViewModel() {
    // 資料
    var self = this;
    self.tasks = ko.observableArray([]);
    self.newTaskText = ko.observable();
    self.incompleteTasks = ko.computed(function() {
        return ko.utils.arrayFilter(self.tasks(), function(task) { return !task.isDone() });
    });

    // 行為
    self.addTask = function() {
        self.tasks.push(new Task({ title: this.newTaskText() }));
        self.newTaskText("");
    };
    self.removeTask = function(task) { self.tasks.remove(task) };
}

ko.applyBindings(new TaskListViewModel());

從ViewModel定義我可以看出來這是一個工作清單,Task物件有抬頭與是否完成。TaskListViewModel物件清單新增、刪除與完成等處理。讀者現在應該已經具備看懂以上程式碼的能力。

View:

<h3>工作</h3>

<form data-bind="submit: addTask">
    新增工作: <input data-bind="value: newTaskText" placeholder="需要做些什麼?" />
    <button type="submit">新增</button>
</form>

<ul data-bind="foreach: tasks, visible: tasks().length > 0">
    <li>
        <input type="checkbox" data-bind="checked: isDone" />
        <input data-bind="value: title, disable: isDone" />
        <a href="#" data-bind="click: $parent.removeTask">刪除</a>
    </li> 
</ul>

你有 <b data-bind="text: incompleteTasks().length"> </b> 個未完成的工作。
<span data-bind="visible: incompleteTasks().length == 0"> - 現在是啤酒時間 !</span>

以下是此範列使用的CSS:

ul { list-style-type: none; margin: 1em 0; background-color: #cde; padding: 1em; border-radius: 0.5em;  }
ul li a { color: Gray; font-size: 90%; text-decoration: none }
ul li a:hover { text-decoration: underline }
input:not([type]), input[type=text] { width: 30em; }
input[disabled] { text-decoration: line-through; border-color: Silver; background-color: Silver; }
textarea { width: 30em; height: 6em; }
form { margin-top: 1em; margin-bottom: 1em; }

花點時間先玩一下這個範例,新增、刪除、完成一些清單內容。

Knockout教學4 - 自訂繫結

繫結(Bindings)

在Knockout.js來解釋MVVM,繫結是什麼內容要加入你的View與ViewModel。繫結是一媒介,它會雙向執行更新:

  • 繫結通知ViewModel改變且對應地更新View的DOM
  • 繫結截取DOM事件且對應地更新ViewModel屬性

Knockout.js有一套靈活和全面內建的繫結(像是text、click、foreach繫結等),但這不意味就此停止,你也可以使用少數程式碼來建立自訂繫結。在任何實際的應用程式中你會發現它有益來封裝常見的UI模式的繫結,這樣就能重覆的使用這些模式。

自訂繫結

例如,knockoutjs.com網站使用自訂繫結來封裝對話方塊,可拖動視窗,程式碼編輯器。

假設你有一個調查頁面的程式碼:

function Answer(text) { 
 this.answerText = text; 
 this.points = ko.observable(1); 
}

function SurveyViewModel(question, pointsBudget, answers) {
    var self = this;
    self.question = question;
    self.pointsBudget = pointsBudget;
    self.answers = $.map(answers, function(text) { return new Answer(text) });
    self.save = function() { alert('To do') };
                       
    self.pointsUsed = ko.computed(function() {
        var total = 0;
        for (var i = 0; i < this.answers.length; i++)
            total += this.answers[i].points();
        return total;        
    }, this);
}

ko.applyBindings(new SurveyViewModel("哪些因素會影響您的技術選擇?", 10, [
   "功能、相容性、定價-那些無聊的東西",
   "在駭客新聞上如何經常被提到",    
   "是否容易學習與使用",        
   "專案上的可信任度"
]));

View:

<h3 data-bind="text: question"></h3>
<p>請將 <b data-bind="text: pointsBudget"></b> 點平均分配至選項。</p>

<table>
    <thead><tr><th>選項</th><th>重要性</th></tr></thead>
    <tbody data-bind="foreach: answers">
        <tr>
            <td data-bind="text: answerText"></td>
            <td><select data-bind="options: [1,2,3,4,5], value: points"></select></td>
        </tr>    
    </tbody>
</table>

<h3 data-bind="visible: pointsUsed() > pointsBudget">你使用超過的點數,請刪減一些。</h3>
<p>你有 <b data-bind="text: pointsBudget - pointsUsed()"></b> 點可以使用。</p>
<button data-bind="enable: pointsUsed() <= pointsBudget, click: save">完成</button>

現在,我們要來改善此應用程式:

  • 使用動畫效果提示「你使用超過的點數,請刪減一些。」
  • 改善「完成」按鈕的樣式
  • 使用星星評分來換代下拉式選單給點數

Knockout教學3 - Single Page Applications, SPA

單頁應用程式

許多最現代、靈敏和基於Web的UI已超越傳統 Ajax,已成為單個頁面的應用程式(single page applications):訪客可以在單一的頁面巡覽像本地應用程序的速度。最著名的例子可能是GMail,但這些日子裡,它已經是一種越來越普遍的技術。

hash-based與pushState巡覽

這種應用程式使用hash-based或pushState巡覽以支援前進/後退手勢和書籤。如果讀者不熟悉這種技術的工作原理,請參閱以下簡短的解釋。

hash-based巡覽:訪問者的位置是儲存在URL hash的虛擬巡覽空間裡,hash符號的後面是URL的一部分(例如,/my/app/#category=shoes&page=4),每當URL hash有變動,瀏覽器不會發出HTTP請求來獲取新的一頁。相反的是,它只會把新的URL加入它的前進或後退歷史清單中,並在此網頁運作的腳本中公開並更新新URL hash。該腳本通知新URL hash和動態更新UI並顯示對應的項目內容(例如,”#category=shoes&page=4”代表鞋子目錄第四頁)。

這樣就可以在單個頁面應用程式中支援前進與後退按鈕巡覽(例如,按下”後退鍵”移至前一個URL hash),和有效地使用虛擬位置並可和書籤共同。

pushState是一個提供了另一種不同的方法來更改當前URL的HTML5 API,它將插入新的前進和後退和歷史項目,而不會觸發頁面載入。這不同於URL hash巡覽,因為不限於更新hash片段,可以更新整個URL。

範例:建置一個網頁郵件用戶端

你有一個簡單ViewModel,它們的內容只有保留資料夾清單。你的第一件工作是呈現它們在畫面上,而且讓它們是可選擇的。

你可以使用foreach繫結來呈顯資料夾清單,在View加入以上程式碼:

<!-- Folders -->
<ul data-bind="foreach: folders">
    <li data-bind="text: $data"></li>
</ul>

如果你現在執行應用程式,你應該有一個圓點清單(bullet-pointed)。這就是好的語意,但不是有吸引力。新增folders類別樣式到<ul>元素來改善:

<ul class="folders" data-bind="foreach: folders">

這使它看來起來更好。

Knockout教學2 - 清單與集合

清單與集合

我們經常在UI元素裡產生重覆的區塊,尤其是呈現清單時,使用者可以在其中新增和移除元素。knockout.js讓你很容易達成此目標,使用可觀察陣列物件(observable arrays)與foreach繫結。

訂位系統

接下來將會建置一個動態UI來訂座位與餐點,在ViewModel裡已經有:

// 姓名與餐點
function SeatReservation(name, initialMeal) {
    var self = this;
    self.name = name;
    self.meal = ko.observable(initialMeal);
}

// 初始化狀態
// 預約ViewModel
function ReservationsViewModel() {
    var self = this;

    // 可選擇餐點(與價格),不能編輯,應該來自伺服器。
    self.availableMeals = [
        { mealName: "Standard (sandwich)", price: 0 },
        { mealName: "Premium (lobster)", price: 34.95 },
        { mealName: "Ultimate (whole zebra)", price: 290 }
    ];    

    // 可編輯資料。
    // 使用可觀察陣列物件,內含兩筆初始化SeatReservation實體資料。
    self.seats = ko.observableArray([
        new SeatReservation("Steve", self.availableMeals[0]),
        new SeatReservation("Bert", self.availableMeals[0])
    ]);
}

ko.applyBindings(new ReservationsViewModel());
  • SeatReservation:一個簡單的JavaScript類別建構式,儲存姓名和餐點的選擇。
  • ReservationsViewModel:ViewModel類別,進行以下處理:
    • availableMeals: 提供餐點資料的JavaScript物件。
    • seats:初始化SeatReservation實體集合的陣列。注意,它是ko.observableArray,這是一個與正規陣列當相的可觀察物件。每次新增或移除項目時,意味著它可以自動觸發ui更新。

ViewModel準備好了之後,以下是預設的View:

<h2>座位預約</h2>

<table>
    <thead>
        <tr>
        <th>姓名</th><th>餐點</th><th>額外費用</th><th></th>
        </tr>
    </thead>
    <!-- Todo: 在tbody產生主體 -->
    <tbody></tbody>
</table>

現在<tbody>還沒有內容,更新<tbody>元素使用foreach繫結,它將呈現位於陣列裡的每個元素:

<tbody data-bind="foreach: seats"></tbody>

我們指定可觀察陣列元素seats來與foreach繫結關聯。然後補充一些<tr>元素以呈現每一個項目:

<tbody data-bind="foreach: seats">
    <tr>
        <td data-bind="text: name"></td>
        <td data-bind="text: meal().mealName"></td>
        <td data-bind="text: meal().price"></td>
    </tr>    
</tbody>

注意,因為meal屬性是一個可觀察物件,前篇說明過,讀取或寫入可觀察物件的值,必須當成函式呼叫。所以必須寫成meal().price來取得次屬性的值,而不是meal.price。

執行應用程式,你應該可以看到簡單關於座位預約的表格資訊。

foreach是流程控制繫結一個份子,包含有foreach、if、ifnot和with。這些使能夠構建任何種類的反覆運算(iterative)、條件運算或或基於動態ViewModel的巢狀UI。

Knockout教學1 - Knockout.js與MVVM模式簡介

Knockout.js與MVVM模式簡介

本篇教學會教讀者使用knockout.js的Model-View-ViewModel(MVVM)模式建立網頁UI的基礎知識。

讀者將學習如何定義UI外觀使用Views和宣告式繫結(declarative bindings)。它的資料和行為使用ViewModels和observables,和如何一切都保持在同步中自動由Knockout.js的相依追蹤(dependency tracking)(即使有任意鏈結(chain)的資料)。

在View使用繫結

下面,擁有一個包含個人資料的ViewModel。

// 這是一個簡單的ViewModel物件
function AppViewModel() {
    this.firstName = "Bert";
    this.lastName = "Bertington";
}

// 啟用Activates knockout.js
ko.applyBindings(new AppViewModel());

App物件包含兩個屬性,firstName與lastName。ko.applyBindings()是第一行knockout.js的程式碼,ko長knockout.js的關鍵字,applyBindings()方法是指定要繫結至那個ViewModel物件。

在View(專指呈現UI所在的地方,通常是指HTML)裡呈現個人資料,現在只呈現”todo”:

<!-- 這是View,HTML定義如何呈何UI -->
<p>姓: <strong>todo</strong></p>
<p>名: <strong>todo</strong></p>

修改View裡的<strong>元素,新增data-bind屬性來呈現個人名稱:

<p>姓: <strong data-bind="text: firstName"></strong></p>
<p>名: <strong data-bind="text: lastName"></strong></p>

data-bind屬性讓Knockout.js可以讓你以宣告方式將DOM元素與ViewModel屬性產生關聯。使用text繫結分配文字給DOM元素。

就這樣,我們完成了第一個有View有ViewModel的knockout.js網頁。

讓資料可編輯

當然,我們不限於呈現靜態資料。我們可以使用value繫結以及一些HTML元素,例如,<input>元素,使資料可以編輯。在View裡新增以下標籤:

<p>姓: <input data-bind="value: firstName" /></p>
<p>名: <input data-bind="value: lastName" /></p>

重新執行應用程式。我們已經可以編輯資料,編輯後沒有發生什麼事。當然,我們希望發生一些事…