クルクルするやつをチョット豪華にする
メソッドを用意します
/* * ローダー表示、表示中判定 */ export function isBusy(value?: boolean): boolean { // オフライン if (isOffline() === true) { return true; } let loader = $(".ui-loader"); if (typeof value === "undefined") { return loader.isVisible(); } if (value === true) { if (loader.isVisible() === true) { return true; } $("body").addClass("ui-disabled").css("opacity", 1); let content = ""; content += "<div class='ui-img-loading'></div>"; content += "<span class='ui-icon-loading'></span>"; content += "<h1>Wait…</h1>"; // html表示、他の設定も必要 $.mobile.loading("show", { html: content , textVisible: true , textonly: false }); // 中央揃え let h = loader.outerHeight(); let w = loader.outerWidth(); loader.css({ "margin-top": -h / 2 , "margin-left": -w / 2 }); return false; } else { $.mobile.loading("hide"); $("body").removeClass("ui-disabled"); return false; } } }
表示文字は面倒なので固定です
- cm.isBusy() 表示中か判定します
- cm.isBusy(true) 表示します 既に表示中の場合はtrueを返します
- cm.isBusy(true) 非表示にします
/* Loader */ div.ui-loader { width: auto; min-width: 12.5em; border: 1px solid #ccc; } div.ui-loader > div.ui-img-loading { background-repeat: no-repeat; background-position-y: center; background-position-x: center; background-size: contain; background-color: white; -moz-background-origin: content-box; -webkit-background-origin: content-box; background-origin: content-box; }
表示する画像はCSSで追加、app.cssかhtmlに記述します
/Views/Table/Index.html
<head> .... <style type="text/css"> .... .ui-loader > .ui-img-loading { background-image: url('../imgs/logo.png'); height: 150px; width: 300px; } </style> </head>
サンプルです
ツールバーのrefreshアイコンのボタンにcmd='refresh'としておきます
/Src/table.ts
namespace Page1 { ... /* * ツールバーボタン押下 */ $(document).on("click", "#page1_toolbar1 .ui-btn", function (event: Event) { let cmd = $(this).attr("cmd"); switch (cmd) { case "refresh": if (cm.isBusy(true) === true) { return; } setTimeout(function () { cm.isBusy(false); }, 10000); break; } }); ... }
クルクルして10秒たったら消えます
トースト焼けました
ウィンドウ下端から出てくる通知メッセージを作成してみます
interface ToasterOptions { timeoutSec?: number; // 閉じるまでの秒数 top?: boolean; // 上から float?: boolean; // 浮かせて表示 } namespace Toaster { "use strict"; let _timeoutHandle = 0; /* * オープン */ export function open(messages: string | JQuery, options?: ToasterOptions) { close(); if (!messages) { return; } let cls = "ui-toaster"; if (options && options.top) { cls += " top"; } if (options && options.float) { cls += " float"; } let content = ""; content += "<div class='" + cls + "'>"; content += " <div>"; content += " </div>"; content += "</div>"; let toast = $(content); toast.children("div").append(messages); // クリックして非表示 toast.off("click").on("click", function (event: Event) { close(); }); // 自動非表示 let autoHideSec = 0; if (options && options.timeoutSec && options.timeoutSec > 0) { autoHideSec = options.timeoutSec; } if (autoHideSec > 0) { _timeoutHandle = setTimeout( function () { close(); } , autoHideSec * 1000); } // 表示 $("body").append(toast); toast.slideDown(); } /* * クローズ */ export function close() { if (_timeoutHandle) { clearTimeout(_timeoutHandle); _timeoutHandle = 0; } let toast = $(".ui-toaster:visible"); if (toast) { toast.slideUp(500, function () { toast.remove(); }); } else { $(".ui-toaster").remove(); } } }
/* Toaster */ div.ui-toaster { display: none; position: fixed; top: auto; bottom: 0; border: none; width: 100%; text-align: center; z-index: 1003; background-color: rgba(0,0,0,0.5); color: white; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; } div.ui-toaster.float { width: auto; left: 50%; -moz-transform: translate(-50%, 0); -ms-transform: translate(-50%, 0); -o-transform: translate(-50%, 0); -webkit-transform: translate(-50%, 0); transform: translate(-50%, 0); margin-right: -50%; bottom: 1em; } div.ui-toaster.top { top: 0; bottom: auto; } div.ui-toaster.top.float { top: 1em; } div.ui-toaster > div { padding: 1em; display: inline-block; }
クリックすると閉じます
表示する時は一旦全て閉じます
使い方です
// 下から浮かせて表示、3秒後に閉じる let content = "<p>焼けました</p><p>焼けました</p>"; Toaster.open(content, { timeoutSec: 3, float: true }); // 上から浮かせて表示 let content = "<p>焼けました</p>"; Toaster.open(content, { top: true, float: true }); // 下から全幅で表示、3秒後に閉じる let content = "<p>焼けました</p><p>焼けました</p>"; Toaster.open(content, { timeoutSec: 3 }); // 上から全幅で表示 let content = "<p>焼けました</p>"; Toaster.open(content, { top: true });
ローダーに進捗を
ファイルのアップロードなんかをするときのクルクルに進捗を付けてみます
ui-loaderクラスを探してプログレスバー替りのdivを挿入します
ui-progress-loadingクラスをでっちあげておきます
あとは$.ajaxSettings.xhr()でとれた値に合わせて幅を変更するだけです
何時、どれだけ進むかは解りません
なすがままです
cm.ts
/* * AJAX進捗表示 */ export function ajaxLoaderProgress() { let x = $.ajaxSettings.xhr(); if (x.upload) { x.upload.addEventListener("progress", loaderProgress, false); } return x; } export function loaderProgress(e: any) { let loader = $(".ui-loader"); let progress = loader.children(".ui-progress-loading"); if (progress.length <= 0) { progress = $("<div class='ui-progress-loading'></div>"); loader.append(progress); } let v = Math.floor((e.loaded / e.total * 10000) / 100); progress.width(v + "%"); }
cm.css
div.ui-loader > div.ui-progress-loading { width: 0; height: 1em; background-color: #ccc; margin: 0.3em; }
使い方1
$.ajax({ xhr: cm.ajaxLoaderProgress })
使い方2
let xhr = new XMLHttpRequest(); xhr.onprogress = cm.loaderProgress;
HandsonTableの編集にも手を出しておく
HandsonTableは標準で優れた編集機能を有しています
カラムの設定時にreadOnly:trueとすれば正にEXCELライクな編集機能が手に入ります
引っかかることもさくサックサクに動きます
エディタも色々と用意されており、もうお腹いっぱいです
ここでは編集開始のイベントでエディタの代わりにPopup等を使用して編集機能を作成してみます
先にデータクラスに数量を追加します(抜けてました)
/Sec/app.ts
namespace App { /* * テーブルに表示するデータクラス */ export class TableData { .... quantity = 0; .... } export function duildTableData(): TableData[] { let dts: TableData[] = []; for (let i = 0; i < 100; i++) { let dt = new TableData; .... dt.quantity = i + 1; // 適当に値設定 .... dts.push(dt); } return dts; }
カラム設定で編集を有効にします
/Src/table.ts
function getColumns(): Object[] { return [ { type: "text" //, title: "番号" // colHeaderで設定する場合は設定しない , data: "data_no" , readOnly: false // 編集可とするカラムのみfalse設定 , colWidths: 100 , editor: CustomEditor // 作成するカスタムエディタ名 } , { type: "text" //, title: "状態" , data: "data_state" , readOnly: false , colWidths: 100 , renderer: htmlRenderer , editor: CustomEditor } , { type: "text" //, title: "日付" , data: "data_date" , readOnly: false , colWidths: 100 , editor: CustomEditor } , { type: "text" //, title: "名称" , data: "data_name" , readOnly: false , colWidths: 300 , editor: CustomEditor } , { type: "numeric" , title: "数量" , data: "quantity" , readOnly: false , colWidths: 50 , editor: CustomEditor }
続けてカスタムエディタを作成
let CustomEditor = Handsontable.editors.TextEditor.prototype.extend(); { CustomEditor.prototype.beginEditing = function () { // 編集開始前 switch (this.prop) { case "data_state": // 状態編集 { let row = this.row; let dt = <App.TableData>_ht.getSourceDataAtRow(row); // 直接データ取り出し let dlg = $("#page1_state_popup"); let lst = dlg.find("ul.ui-listview"); lst.find("li > a").off("click").on("click", function (event: Event) { let cd = parseInt($(this).attr("cd"), 0); let nm = $(this).attr("title"); if (isNaN(cd) === false) { dt.data_state_cd = cd; dt.data_state = nm; _ht.render(); Popup.close(); } }); // ポップアップ表示 Popup.open(dlg, { positionTo: this.TD , focusSelector: lst }); } break; case "data_date": break; case "data_name": // 名称編集 Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments); // 通常動作 break; case "quantity": // 数量編集 { let row = this.row; let dt = <App.TableData>_ht.getSourceDataAtRow(row); // 直接データ取り出し let dlg = $("#page1_quantity_popup").css("width", "17em"); let ipt = dlg.find(".ui-input-text > input").val(dt.quantity); /* * ポップアップボタン押下 */ dlg.find(".ui-btn").off("click").on("click", function (event: Event) { let cmd = $(this).attr("cmd"); switch (cmd) { case "apply": // OK let v = parseInt(ipt.val(), 0); if (isNaN(v) === false) { dt.quantity = v; _ht.render(); } Popup.close(); break; } }); // ポップアップ表示 Popup.open(dlg, { positionTo: this.TD , focusSelector: ipt , onOpend: function () { ipt.NumberSpin({ digits: 0, slider: true }); } }); } break; default: } }; }
任意名のエディタは適当な組み込みエディタから継承します
編集開始前イベントは作成したCustomEditorから取ることになります
”editor: CustomEditor”としたカラムは編集しようとすると全てこちらに来ます(無論個々にエディタを作成しても構いません)
バインドされたプロパティ名(prop)で分岐させています
beginEditingで何もしないと編集状態にはなりません
”Handsontable.editors.TextEditor.prototype.beginEditing.apply(this, arguments);”なる呪文が必要になります
「状態」(data_state)→Popupでリストを表示
「日付」(data_date)→編集禁止
「名称」(data_name)→デフォルト動作
「数量」(quantity)→Popupで数値入力
としています
HTMLに表示するPopupを追加して完成
/Views/Table/Index.vbhtml
<body class="ui-nodisc-icon ui-alt-icon ui-desktop"> <div data-role="page" id="page1"> .... <div data-role="popup" data-history="false" id="page1_state_popup" data-arrow="true"> <ul data-role="listview" data-icon="false"> <li><a href="#" cd="11" title="State11">状態1</a></li> <li><a href="#" cd="22" title="State22">状態2</a></li> <li><a href="#" cd="33" title="State33">状態3</a></li> <li><a href="#" cd="44" title="State44">状態4</a></li> <li><a href="#" cd="55" title="State55">状態5</a></li> </ul> </div> <div data-role="popup" data-history="false" id="page1_quantity_popup" data-arrow="true"> <div role="main" class="ui-content"> <form novalidate="novalidate"> <input type="number" value="" max="10" min="1" step="1"> </form> </div> <div data-role="footer"> <div class="ui-toolbar"> <div class="ui-right-panel"> <a href="#" class="ui-btn ui-btn-active" cmd="apply">OK</a> <a href="#" class="ui-btn ui-popup-close">キャンセル</a> </div> </div> </div> </div> .... </div> </body>
HandsonTableでヘッダーを弄ってみる
カラムヘッダ部にボタンを付けてポップアップなんぞ表示してみます
カラムヘッダに細工をする場合は、オプションのcolHeaders: trueをcolHeaders: function()に変更してメソッド内で処理します
columns: GetColumnsで指定しているtitleを削除しなくては反映されません
/Src/table.ts
/* * テーブル */ namespace HTable { /* * 作成 */ export function build() { _ht = new Handsontable($("#list_table")[0], { data: null , columns: getColumns() // 列設定 , colHeaders: getColHerders // 列見出し .... }); Ht.setFullSize(_ht); // 全面表示 } /* * カラム設定 */ function getColumns(): Object[] { return [ { type: "text" //, title: "番号" // colHeaderで設定する場合は設定しない , data: "data_no" , readOnly: true , colWidths: 100 } , { type: "text" //, title: "状態" , data: "data_state" , readOnly: true , colWidths: 100 , renderer: htmlRenderer } , { type: "text" //, title: "日付" , data: "data_date" , readOnly: true , colWidths: 100 } , { type: "text" //, title: "名称" , data: "data_name" , readOnly: true , colWidths: 300 } .... ]; } /* * カラムヘッダー設定 */ function getColHerders(col: number): string { // // columnsのtitleが設定されてる列では呼ばれません // columnSortingとの併用は考慮外 // if (_ht) { let prop = _ht.colToProp(col); switch (prop) { case "data_no": { // 丸ごとボタン let content = ""; content += "<a href='#'"; content += " class='ui-btn ui-btn-icon-left ui-icon-search'"; content += " cmd='data_no'"; content += ">"; content += "番号"; content += "</a>"; return content; } case "data_state": { // アイコン付加 let content = ""; content += "<div"; content += " class='ui-icon ui-icon-tag-b'"; content += " style='display:inline-block;'"; content += ">"; content += "状態"; content += "</div>"; return content; } case "data_date": { // テキストなしボタン付加 let content = ""; content += "<div>"; content += "日付"; content += " <a href='#'"; content += " class='ui-btn ui-btn-icon-notext ui-icon-calendar ui-btn-inline'"; content += " cmd='data_date'"; content += " >"; content += "</a>"; content += "</div>"; return content; } case "data_name": { // テキストありボタン付加 let content = ""; content += "<div>"; content += "名称"; content += " <a href='#'"; content += " class='ui-btn ui-btn-icon-left ui-icon-bars ui-btn-inline'"; content += " cmd='data_name'"; content += " >"; content += "選択"; content += "</a>"; content += "</div>"; return content; } } } } .... }
カラムヘッダのボタンクリックイベントを設定します
ポップアップを表示するのみに留めておきます
/Src/table.ts
/* * カラムヘッダボタン押下 */ $(document).on("click", "#list_table thead > tr > th .ui-btn", function (event: Event) { let cmd = $(this).attr("cmd"); let txt = ""; switch (cmd) { case "data_no": txt = "番号カラムヘッダ"; break; case "data_date": txt = "日付カラムヘッダ"; break; case "data_name": txt = "名称カラムヘッダ"; break; } if (txt) { let dlg = $("#page1_popup"); dlg.children("p").text(txt); dlg.off("click").on("click", function (event: Event) { Popup.close(); }); Popup.open(dlg, { positionTo: this }); } });
htmlにポップアップを追加します
/Views/Table/Index.vbhtml
<div data-role="popup" data-history="false" id="page1_popup" data-arrow="true"> <p></p> </div>
CSSでheaderの体裁を整えて完了
div.handsontable span.colHeader { display: block; } div.handsontable span.colHeader.columnSorting { display: inline-block; } .ui-page div.handsontable span.colHeader .ui-btn { margin: 0; /*background-color: transparent;*/ border-color: #ccc; padding-top: 0.3em; padding-bottom: 0.3em; font-size: 14px; } .ui-page div.handsontable span.colHeader .ui-btn.ui-btn-icon-notext { padding: 0; } .ui-page div.handsontable span.colHeader > div > .ui-btn { margin-top: -0.1em; }
ヘッダーフッターも一捻り
jQuery Mobileさん、iosやandroid上だとinputにフォーカスが行く度にヘッダーとフッターが隠れます
表示面積を大きく取るためなのでしょうが、うっとおしいです
よくある動作ですが、うっとおしいです
CSSを上書きして捻り潰してくれます
/Src/cm.css
/* Mobile Header/Footer Auto Slideup/Down Cancel */ .ui-header-fixed.ui-fixed-hidden { top: -1px; padding-top: 1px; position: fixed; } .ui-footer-fixed.ui-fixed-hidden { bottom: -1px; padding-bottom: 1px; position: fixed; } .ui-header-fixed.slidedown.in, .ui-header-fixed.slidedown.out, .ui-header-fixed.slidedown.reverse, .ui-footer-fixed.slideup.in, .ui-footer-fixed.slideup.out, .ui-footer-fixed.slideup.reverse { -o-animation-name: none; -moz-animation-name: none; -webkit-animation-name: none; animation-name: none; -o-transform: none; -ms-transform: none; -moz-transform: none; -webkit-transform: none; transform: none; }
ついでにヘッダにロゴ画像を出して其れっぽい感じにします
/Src/cm.css
/* Header Image */ .ui-header > .ui-title.ui-img-app { background-repeat: no-repeat; background-position-y: center; background-position-x: left; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; box-sizing: border-box; }
/Src/app.css
/* For Mobile */ body:not(.ui-desktop) ul.ui-listview > li.ui-li-divider, body:not(.ui-desktop) div.ui-header > .ui-title { border-color: #d00; background-color: #d00; color: white; } body:not(.ui-desktop) .ui-checkbox-on:after, body:not(.ui-desktop) .ui-flipswitch-active.ui-flipswitch, body:not(.ui-desktop) .ui-btn-active { border-color: #d00!important; background-color: #d00 !important; color: white; } /* For Desktop */ body.ui-desktop ul.ui-listview > li.ui-li-divider, body.ui-desktop div.ui-header > .ui-title { border-color: #0bb; background-color: #0bb; color: white; } body.ui-desktop .ui-checkbox-on:after, body.ui-desktop .ui-flipswitch-active.ui-flipswitch, body.ui-desktop .ui-btn-active { border-color: #0bb!important; background-color: #0bb!important; color: white; } /* Header Title Logo */ .ui-header > .ui-title.ui-img-app { background-image: url('../imgs/logo.png'); background-size: auto 5em; /* image height*/ height: 5em; /* image height*/ padding-left: 18em; /* image width*/ border-color: white !important; background-color:white !important; color: black !important; }
タイトルになるタグにui-img-appを付加し、app.cssで表示ロゴとサイズを指定します
bodyに付加してui-desktopでデスクトップとモバイルの色を管理します
<div data-role="header" data-position="fixed" data-fullscreen="false" data-tap-toggle="false"> <h1 class="ui-img-app ui-title-left">メニュー</h1> .... </div>
HandsonTableでセルに色やアイコンを
HandsonTableのセルに色を付け、アイコンを付けてみます
http://andymatthews.net/code/jQuery-Mobile-Icon-Pack/builder/からアイコンを用意します
/Src/App.css
.ui-icon-tag-w:after { background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%20Tiny%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11-tiny.dtd%22%3E%3Csvg%20version%3D%221.1%22%20baseProfile%3D%22tiny%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2216px%22%20height%3D%2216px%22%20viewBox%3D%220%200%20500%20500%22%20xml%3Aspace%3D%22preserve%22%3E%20%3Cpath%20d%3D%22M125%20125q0-14.788-10.463-25.251t-25.251-10.463-25.251%2010.463-10.463%2025.251%2010.463%2025.251%2025.251%2010.463%2025.251-10.463%2010.463-25.251zM422.712%20285.714q0%2014.788-10.324%2025.112l-136.998%20137.277q-10.882%2010.324-25.391%2010.324-14.788%200-25.112-10.324l-199.498-199.777q-10.603-10.324-17.997-28.181t-7.394-32.645v-116.071q0-14.509%2010.603-25.112t25.112-10.603h116.071q14.788%200%2032.645%207.394t28.46%2017.997l199.498%20199.219q10.324%2010.882%2010.324%2025.391z%22%20fill%3D%22%23ffffff%22%20%2F%3E%3C%2Fsvg%3E'); background-size: 20px; border-radius: 0; } .ui-icon-tag-b:after { background-image: url('data:image/svg+xml;charset=US-ASCII,%3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3C!DOCTYPE%20svg%20PUBLIC%20%22-%2F%2FW3C%2F%2FDTD%20SVG%201.1%20Tiny%2F%2FEN%22%20%22http%3A%2F%2Fwww.w3.org%2FGraphics%2FSVG%2F1.1%2FDTD%2Fsvg11-tiny.dtd%22%3E%3Csvg%20version%3D%221.1%22%20baseProfile%3D%22tiny%22%20id%3D%22Layer_1%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20xmlns%3Axlink%3D%22http%3A%2F%2Fwww.w3.org%2F1999%2Fxlink%22%20x%3D%220px%22%20y%3D%220px%22%20width%3D%2216px%22%20height%3D%2216px%22%20viewBox%3D%220%200%20500%20500%22%20xml%3Aspace%3D%22preserve%22%3E%20%3Cpath%20d%3D%22M125%20125q0-14.788-10.463-25.251t-25.251-10.463-25.251%2010.463-10.463%2025.251%2010.463%2025.251%2025.251%2010.463%2025.251-10.463%2010.463-25.251zM422.712%20285.714q0%2014.788-10.324%2025.112l-136.998%20137.277q-10.882%2010.324-25.391%2010.324-14.788%200-25.112-10.324l-199.498-199.777q-10.603-10.324-17.997-28.181t-7.394-32.645v-116.071q0-14.509%2010.603-25.112t25.112-10.603h116.071q14.788%200%2032.645%207.394t28.46%2017.997l199.498%20199.219q10.324%2010.882%2010.324%2025.391z%22%20fill%3D%22%23000000%22%20%2F%3E%3C%2Fsvg%3E'); background-size: 20px; border-radius: 0; }
セル内の変更はカラム設定のrendererに任意のFunctionを割り当てることで実現します
/Src/table.ts
function getColumns(): Object[] { return [ .... , { type: "text" , title: "状態" , data: "data_state" , readOnly: true , colWidths: 100 , renderer: htmlRenderer } .... ]; } .... /* * HTMLセル描画 */ function htmlRenderer(instance: any, td: HTMLTableDataCellElement, row: number, col: number, prop: string, value: any, cellProperties: any) { if (!_ht || _ht.countRows() <= row) { return; } switch (prop) { case "data_state": { let icon = "none"; let dt = <App.TableData>_ht.getSourceDataAtRow(row); // 直接データ取り出し if (dt.data_state_cd % 5 === 0) { $(td).css({ "color": "white" , "background-color": "red" }); icon = "tag"; } $(td).empty().append("<div class='ui-icon ui-icon-" + icon + "-w'>" + value + "</div>"); // $(td).text(value); } break; } }
設定した列のセルが描画される前のコールバックです
バインドされたデータを取得して、tdを弄ります
基本的にただのHTMLなのでどのようにでもなります
ちなみにpropがバインドされたメンバ名になります
サラッと出来上がり