KnockoutJS:変更の受け入れまたは拒否がいかに簡単かという物語

多くの場合、ユーザーインターフェイスには[保存]ボタンと[キャンセル]ボタンがあります。 特に、これらのボタンはフォームでよく使用されます。 現代の世界ではすべてがインターフェースを簡素化しようとしているという事実にもかかわらず、これらのボタンに対する需要は依然としてあります。



今日は、KnockoutJSを使用して、個々のオブザーバブルとビューモデル全体の変更を受け入れ、ロールバックする方法を見つけることを提案します。



KnockoutJSに精通していると、このテーマに関する最高のブログへの2つのリンクをすぐに提供できます。



これらの方法には、除去する必要がある利点と非常に重大な欠点の両方があります。 機能的な弱点



まあ、そして、彼らは個々の観察可能に向けられていますが、私は一度にいくつかの分野で働きたいです。



カットの下で、変更の採用とキャンセルを簡単かつ透過的に行う単純なメカニズムを作成するプロセスを詳細に分析します。







変更の受け入れまたはキャンセルに関して最初に思い浮かぶのは、トランザクションとDBMSでの実装です。 変更の前に、トランザクションを開始してから、コミットまたはロールバックする必要があります。 すべてがシンプルで明確です。



これらのメソッドの類似物をオブザーバブルで作成することを妨げるものは何もありません。 記事の冒頭の例と同様に、簡単に行動してko.transactionableObservable



できます。



 ko.transactionableObservable = function(initialValue) { var result = ko.observable(initialValue); result.beginTransaction = function() { ... }; result.commit = function() { ... }; result.rollback = function() { ... }; return result; } var name = ko.transactionableObservable('habrauser');
      
      







しかし、私はこのアプローチを強く嫌います。 ご覧のとおり、結果は通常の観測可能です。 しかし、observableArrayまたはwriteable dependentObservableへの変更を受け入れる/キャンセルする場合はどうすればよいでしょうか?



Knockoutの2番目のバージョンに登場したエクステンダーが助けになります。



Extenderを使用すると、あらゆる種類のオブザーバブルの動作を非常にエレガントに変更または補完できます。 コードは次のようになります。



 var name = ko.observable('habrauser').extend({editable: true});
      
      







エクステンダーのさへの実装は簡単です:

ko.extenders['myExtender'] = function(observavle, params){}







このアイデアは私にとって価値があるように思えるので、エクステンダーの簡単な実装を作りましょう



 ko.extenders['editable'] = function (target) { var oldValue; var inTransaction = false; target.beginEdit = function () { var currentValue = target(); if (currentValue instanceof Array) { currentValue = currentValue.slice(); // make copy } oldValue = currentValue; inTransaction = true; }; target.commit = function () { inTransaction = false; }; target.rollback = function () { if (inTransaction) { target(oldValue); inTransaction = false; } }; return target; };
      
      







コードをロシア語に翻訳するのはあまり意味がないと思います。 唯一の注意点は、アレイを操作することです。

オブザーバブル内に配列がある場合、この値を「記憶」することはできません。 これは参照データ型であるため、配列への参照ではなく、配列のコピーを覚えておく必要があります。 そして、はい、非参照データ型を含むオブザーバブルのみが意味をなします。 後でこの問題を克服します。



使用例:

 var name = ko.observable().extend({editable: true}); var nameLength = ko.dependentObservable(function() { return name() ? name().length : 0; }); name('user'); // name set to 'user' name.beginEdit(); // begin transaciton name('me'); // name set to 'me', nameLength was recalculated nameLength(); // gives us 2 name.commit(); // transaction commited; values are unchanged since last edit; we could start another one name.rollback(); // nothing happens since transation is commited name.beginEdit(); // begin another transaction name('someone'); // name set to 'someone', nameLength was recalculated name.rollback(); // rollback transaction; name set to initial value 'me', nameLength recalculated name(); // returns 'me'
      
      







また、旗、変化の存在があることは素晴らしいことです。 原理は簡単です:

 target.hasChanges = ko.dependedObservable(function () { var hasChanges = inTransaction && oldValue != target(); return hasChanges; });
      
      







出力は、開いているトランザクションがあり、現在の値がトランザクションの開始時の最初の値と異なる場合にのみ真になります。 使用されるオブザーバブルのいずれかが変更されると、ノックアウトはdependentObservableの値を再カウントすることを思い出してください。



現在の実装は常にfalseを返します。 この理由は非常に単純です:最初の実行時(つまり、宣言の直後)inTransaction == false、つまり、AND演算子の2番目のチェックも実行されないことを意味します。 つまり、dependendObservableは再計算されません。 これを修正するのは非常に簡単です。 inTransaction



変数を「 inTransaction



可能」にします。



したがって、トランザクションの開始時/終了時、および元のオブザーバブルが変更されたときに、値が再カウントされます。 これが必要なものです!



 ko.extenders['editable'] = function (target) { var oldValue; var inTransaction = ko.observable(false); target.beginEdit = function () { var currentValue = target(); if (currentValue instanceof Array) { currentValue = currentValue.slice(); // make copy } oldValue = currentValue; inTransaction(true); }; target.commit = function () { inTransaction(false); }; target.rollback = function () { if (inTransaction()) { target(oldValue); } }; target.hasChanges = deferredDependentObservable(function () { var hasChanges = inTransaction() && oldValue != target(); return hasChanges; }); return target; };
      
      







私に関しては、1つのフィールドに対して非常に堅実な実装を実現しました。 しかし、12個のフィールドがある場合はどうでしょう。 編集する前に、各フィールドでbeginEdit()を実行してから、コミット/ロールバックする必要があります。 あなた自身を掛けることができます。



実装でエクステンダーを使用するという事実により、必要なフィールドが宣言された後に拡張することができ、これらのフィールドのdependentObservableが壊れる心配はありません。 これは、これを非常に自動的に実行できることを意味します。

私の考えをもっと平凡に表現します。



 var user = { firstName : ko.observable('habrauser'), lastName: ko.observable('sapiens') }; user.fullName = ko.dependentObservable(function() { return user.firstName() + ' ' + user.lastName(); }); ko.editable(user); user.beginEdit(); user.firstName('homo'); user.fullName(); // 'homo sapiens' user.hasChanges(); // true user.commit();
      
      







記事の冒頭で、特定の観察可能なトランザクションを作成した場合、現在は任意のオブジェクトをトランザクションに作成しています。 概して、これは非常に簡単に実装されます。

 ko.editable = function (viewModel, autoInit) { var editables = ko.observableArray(); (function makeEditable(rootObject) { for (var propertyName in rootObject) { var property = rootObject[propertyName]; if (ko.isWriteableObservable(property)) { var observable = property; observable.extend({ editable: true }); editables.push(observable); } property = ko.utils.unwrapObservable(property); if (typeof (property) == 'object') { makeEditable(property); } } })(viewModel); viewModel.beginEdit = function () { ko.utils.arrayForEach(editables(), function (obj) { obj.beginEdit(); }); }; viewModel.commit = function () { ko.utils.arrayForEach(editables(), function (obj) { obj.commit(); }); }; viewModel.rollback = function () { ko.utils.arrayForEach(editables(), function (obj) { obj.rollback(); }); }; viewModel.addEditable = function (editable) { editables.push(editable.extend({ editable: true })); }; viewModel.hasChanges = deferredDependentObservable(function () { var editableWithChanges = ko.utils.arrayFirst(editables(), function (editable) { return editable.hasChanges(); }); return editableWithChanges != null; }); };
      
      







はじめにのみ注意が必要です。 プロパティがwriteableObservableであればオブジェクトのすべてのプロパティをバイパスします(そして、考えてみれば、それだけがトランザクション可能になります)。その後、エクステンダーを使用してそれを拡張します。 さらにコードに沿って、オブジェクトがフィールドにある場合(および配列もオブジェクトである場合)、そのフィールドを調べます。



そして、簡単な使用例:



 var user = { FirstName: ko.observable('Some'), LastName: ko.observable('Person'), Address: { Country: ko.observable('USA'), City: ko.observable('Washington') } }; ko.editable(user); user.beginEdit(); user.FirstName('MyName'); user.hasChanges(); // returns `true` user.commit(); user.hasChanges(); // returns `false` user.Address.Country('Ukraine'); user.hasChanges(); // returns `true` user.rollback(); user.Address.Country(); // returns 'USA'
      
      







調査の結果、GitHubに投稿し、ko.editablesという名前のgithub.com/romanych/ko.editablesというコードを作成しました。



単純な使用例ともう少し複雑 例もあります。



最後まで読んでくれたみんなに感謝します。 コードが役に立つことを願っています。



All Articles