マルチレベルの連携メニューは、州と都市の連携、大学と大学と専攻の連携など、一般的なフロントエンド コンポーネントです。シナリオは共通していますが、注意深く分析すると、汎用の無限階層リンク メニューの実装は想像ほど簡単ではない可能性があります。たとえば、サブメニューが同期的に読み込まれるか、非同期的に読み込まれるかを考慮する必要があります。初期値の埋め戻しはフロントエンドで発生しますか?それともバックエンドで発生しますか?非同期でロードされる場合、バックエンド API の戻り形式の厳密な定義はありますか?同期と非同期の共存を実現するのは簡単ですか?さまざまな依存関係を柔軟にサポートできますか?メニューに null 値のオプションはありますか? …一連の問題には慎重に対処する必要がある。
これらの要件を調べた結果、当然のことながら、AngularJS エコシステムで非常に適切なプラグインや命令が見つかりませんでした。そのため、自分で実装してみなければなりませんでした。
この記事の実装は AngularJS に基づいていますが、アイデアは一般的なものであり、他のフレームワーク ライブラリに精通している学生も読むことができます。
まず、AngularJS のレンダリングはフロントエンドで行われるため、バックエンドで既存の値に基づいてすべてのレベルのメニューのオプションを取得してレンダリングするという以前のソリューションを再整理しました。テンプレート層はあまり適切ではありません。多くの学生と同様に、私も個人的にはこの実装が好きではありません。オプションの最初のプルと初期値のバックフィルがバックエンドで完了したとしても、読み込みが完了するため、何度も実行されます。サブメニューの内容は API に依存するため、フロントエンドも onchange イベントをリッスンして Ajax インタラクションを実行する必要があります。つまり、単純な第 2 レベルのリンケージ メニューでは、フロント エンドとバック エンドの間でロジックを分割する必要があります。この方法は価値がありません。賞賛の。
同期および非同期の読み込み方法に関しては、ほとんどの場合ステップ全体が非同期ですが、オプションが少ない一部のリンケージ メニューでは、API ですべてのデータを取得し、処理し、キャッシュしてサブサーバーに提供することもできます。 -menu レンダリングに使用されます。したがって、同期レンダリング方法と非同期レンダリング方法の両方をサポートする必要があります。
API の戻り形式の問題に関しては、新しいプロジェクトに取り組んでいる場合、またはバックエンド プログラマーが需要の変化にすぐに対応できる場合、またはフロントエンドの学生自身がフルスタックである場合、この問題は発生しない可能性があります。非常に重要ですが、多くの場合、私たちがやり取りする API はプロジェクトの他の部分で使用されているため、この記事では JSON 形式を調整するのは簡単ではありません。サブメニュー オプション データはディレクティブ自体から分離され、特定のビジネス ロジックによって処理されます。
柔軟な依存関係のサポートを実装するにはどうすればよいですか?最も一般的な線形依存関係に加えて、ツリー依存関係、逆ピラミッド依存関係、さらには複雑なネットワーク依存関係もサポートする必要があります。これらのビジネス シナリオが存在するため、依存関係をロジックにハードコーディングするのは複雑です。トレードオフの後、コンポーネントはイベントを通じて通信します。
要件は次のように要約されます:
* フロントエンドでの初期値バックフィルをサポート
* サブセット メニュー オプションの同期および非同期取得をサポート
* メニュー間の柔軟な依存関係 (線形依存関係、ツリー依存関係、逆ピラミッド依存関係、メッシュ依存関係など) をサポート
* メニューの空の値オプション (option[value=""])
をサポートします。
* サブセット メニューの取得ロジックはコンポーネント自体から切り離されています
* イベント駆動型の、すべてのレベルのメニューは論理的に互いに独立しており、相互に影響しません
マルチレベルのリンク メニューは、AngularJS の select タグの本来の動作にさらに影響を与えるため、その後のプログラミングを容易にし、潜在的な競合を減らすために、この記事では項目で
1. まず最初の質問、フロントエンドで初期値を埋め戻す方法について考えてみましょう
マルチレベル連携メニューの最も明白な特徴は、上位レベルのメニューが変更された後、下位レベルのメニューが (同期または非同期で) 再レンダリングされることです。値をバックフィルするプロセスでは、段階的にバックフィルする必要がありますが、ページがロードされたとき (またはルートがロードされたとき、コンポーネントがロードされたときなど)、このプロセスは即座に完了することはできません。特に AngularJS では、オプションのレンダリング処理が ngModel のレンダリングより前に行われる必要があります。そうしないと、たとえオプションに対応する値があっても、一致するオプションが見つかりません。
解決策は、まず命令のリンクフェーズでモデルの初期値を保存し、それを null 値に割り当て ($setViewValue を呼び出すことができます)、次にレンダリングの完了後に非同期的に元の値に割り当て直します。
2. サブオプション取得の特定のロジックを分離し、同期メソッドと非同期メソッドの両方をサポートする方法
スコープ内で「=」クラス属性を使用して、外部関数をディレクティブのリンク メソッドに公開できます。このメソッドを実行するたびに、Promiseインスタンスであるか(またはthenメソッドを持つか)が判定され、その判定結果に基づいて同期レンダリングか非同期レンダリングが決定されます。このような分離により、ユーザーは渡された外部関数でのレンダリング方法を簡単に決定できます。コールバック関数の醜さを軽減するために、then メソッドを使用して同期戻り値をオブジェクトとしてカプセル化することもできます。以下に示すように:
// scope.source为外部函数 var returned = scope.source ? scope.source(values) : false; !returned || (returned = returned.then ? returned : { then: (function (data) { return function (callback) { callback.call(window, data); }; })(returned) }).then(function (items) { // 对同步或异步返回的数据进行统一处理 }
3. 如何实现菜单间基于事件的通信
大体上还是通过订阅者模式实现,需要在directive上声明依赖;由于需要支持复杂的依赖关系,应该支持一个子集菜单同时有多个依赖。这样在任何一个所依赖的菜单变化时,我们都可以通过如下方式进行监听:
scope.$on('selectUpdate', function (e, data) { // data.name是变化的菜单,dependents是当前菜单所声明的依赖数组 if ($.inArray(data.name, dependents) >= 0) { onParentChange(); } }); // 并且为了方便上文提到的source函数对于变动值的调用,可以对所依赖的菜单进行遍历并保存当前值 var values = {}; if (dependents) { $.each(dependents, function (index, dependent) { values[dependent] = selects[dependent].getValue(); }); }
4. 处理两类过期问题
容易想到的是异步过期的问题:设想第一级菜单发生变化,触发对第二级菜单内容的拉取,但网速较慢,该过程需要3秒。1秒后用户再次改变第一级菜单,再次触发对第二级菜单内容的拉取,此时网速较快,1秒后数据返回,第二级菜单重新渲染;但是1秒后,第一次请求的结果返回,第二级菜单再次被渲染,但事实上第一级菜单此后已经发生过变化,内容已经过期,此次渲染是错误的。我们可以用闭包进行数据过期校验。
不容易想到的是同步过期(其实也是异步,只是未经io交互,都是缓冲时间为0的timeout函数)的问题,即由于事件队列的存在,稍不谨慎就可能出现过期,代码中会有相关注释。
5. 支持空值选项的细节问题
对于空值的支持本来觉得是一个很简单的问题,即可,但实际编码中发现,在directive的link中,由于此option的link过程并未开始,option标签被实际上移除,只剩下相关注释占位。AngularJS认为该select不含有空值选项,于是报错。解决方案是弃用ng-if,使用ng-show。这二者的关系极其微妙有意思,有兴趣的同学可以自己研究~
以上就是编码过程中遇到的主要问题,欢迎交流~
directive('multiLevelSelect', ['$parse', '$timeout', function ($parse, $timeout) { // 利用闭包,保存父级scope中的所有多级联动菜单,便于取值 var selects = {}; return { restrict: 'CA', scope: { // 用于依赖声明时指定父级标签 name: '@name', // 依赖数组,逗号分割 dependents: '@dependents', // 提供具体option值的函数,在父级change时被调用,允许同步/异步的返回结果 // 无论同步还是异步,数据应该是[{text: 'text', value: 'value'},]的结构 source: '=source', // 是否支持控制选项,如果是,空值的标签是什么 empty: '@empty', // 用于parse解析获取model值(而非viewValue值) modelName: '@ngModel' }, template: '' // 使用ng-show而非ng-if,原因上文已经提到 + '<option ng-show="empty" value="">{{empty}}</option>' // 使用朴素的ng-repeat + '<option ng-repeat="item in items" value="{{item.value}}">{{item.text}}</option>', require: 'ngModel', link: function (scope, elem, attr, model) { var dependents = scope.dependents ? scope.dependents.split(',') : false; var parentScope = scope.$parent; scope.name = scope.name || 'multi-select-' + Math.floor(Math.random() * 900000 + 100000); // 将当前菜单的getValue函数封装起来,放在闭包中的selects对象中方便调用 selects[scope.name] = { getValue: function () { return $parse(scope.modelName)(parentScope); } }; // 保存初始值,原因上文已经提到 var initValue = selects[scope.name].getValue(); var inited = !initValue; model.$setViewValue(''); // 父级标签变化时被调用的回调函数 function onParentChange() { var values = {}; // 获取所有依赖的菜单的当前值 if (dependents) { $.each(dependents, function (index, dependent) { values[dependent] = selects[dependent].getValue(); }); } // 利用闭包判断io造成的异步过期 (function (thenValues) { // 调用source函数,取新的option数据 var returned = scope.source ? scope.source(values) : false; // 利用多层闭包,将同步结果包装为有then方法的对象 !returned || (returned = returned.then ? returned : { then: (function (data) { return function (callback) { callback.call(window, data); }; })(returned) }).then(function (items) { // 防止由异步造成的过期 for (var name in thenValues) { if (thenValues[name] !== selects[name].getValue()) { return; } } scope.items = items; $timeout(function () { // 防止由同步(严格的说也是异步,注意事件队列)造成的过期 if (scope.items !== items) return; // 如果有空值,选择空值,否则选择第一个选项 if (scope.empty) { model.$setViewValue(''); } else { model.$setViewValue(scope.items[0].value); } // 判断恢复初始值的条件是否成熟 var initValueIncluded = !inited && (function () { for (var i = 0; i < scope.items.length; i++) { if (scope.items[i].value === initValue) { return true; } } return false; })(); // 恢复初始值 if (initValueIncluded) { inited = true; model.$setViewValue(initValue); } model.$render(); }); }); })(values); } // 是否有依赖,如果没有,直接触发onParentChange以还原初始值 !dependents ? onParentChange() : scope.$on('selectUpdate', function (e, data) { if ($.inArray(data.name, dependents) >= 0) { onParentChange(); } }); // 对当前值进行监听,发生变化时对其进行广播 parentScope.$watch(scope.modelName, function (newValue, oldValue) { if (newValue || '' !== oldValue || '') { scope.$root.$broadcast('selectUpdate', { // 将变动的菜单的name属性广播出去,便于依赖于它的菜单进行识别 name: scope.name }); } }); } }; }]);

CおよびJavaScriptは、WebAssemblyを介して相互運用性を実現します。 1)CコードはWebAssemblyモジュールにコンパイルされ、JavaScript環境に導入され、コンピューティングパワーが強化されます。 2)ゲーム開発では、Cは物理エンジンとグラフィックスレンダリングを処理し、JavaScriptはゲームロジックとユーザーインターフェイスを担当します。

JavaScriptは、Webサイト、モバイルアプリケーション、デスクトップアプリケーション、サーバー側のプログラミングで広く使用されています。 1)Webサイト開発では、JavaScriptはHTMLおよびCSSと一緒にDOMを運用して、JQueryやReactなどのフレームワークをサポートします。 2)ReactNativeおよびIonicを通じて、JavaScriptはクロスプラットフォームモバイルアプリケーションを開発するために使用されます。 3)電子フレームワークにより、JavaScriptはデスクトップアプリケーションを構築できます。 4)node.jsを使用すると、JavaScriptがサーバー側で実行され、高い並行リクエストをサポートします。

Pythonはデータサイエンスと自動化により適していますが、JavaScriptはフロントエンドとフルスタックの開発により適しています。 1. Pythonは、データ処理とモデリングのためにNumpyやPandasなどのライブラリを使用して、データサイエンスと機械学習でうまく機能します。 2。Pythonは、自動化とスクリプトにおいて簡潔で効率的です。 3. JavaScriptはフロントエンド開発に不可欠であり、動的なWebページと単一ページアプリケーションの構築に使用されます。 4. JavaScriptは、node.jsを通じてバックエンド開発において役割を果たし、フルスタック開発をサポートします。

CとCは、主に通訳者とJITコンパイラを実装するために使用されるJavaScriptエンジンで重要な役割を果たします。 1)cは、JavaScriptソースコードを解析し、抽象的な構文ツリーを生成するために使用されます。 2)Cは、Bytecodeの生成と実行を担当します。 3)Cは、JITコンパイラを実装し、実行時にホットスポットコードを最適化およびコンパイルし、JavaScriptの実行効率を大幅に改善します。

現実世界でのJavaScriptのアプリケーションには、フロントエンドとバックエンドの開発が含まれます。 1)DOM操作とイベント処理を含むTODOリストアプリケーションを構築して、フロントエンドアプリケーションを表示します。 2)node.jsを介してRestfulapiを構築し、バックエンドアプリケーションをデモンストレーションします。

Web開発におけるJavaScriptの主な用途には、クライアントの相互作用、フォーム検証、非同期通信が含まれます。 1)DOM操作による動的なコンテンツの更新とユーザーインタラクション。 2)ユーザーエクスペリエンスを改善するためにデータを提出する前に、クライアントの検証が実行されます。 3)サーバーとのリフレッシュレス通信は、AJAXテクノロジーを通じて達成されます。

JavaScriptエンジンが内部的にどのように機能するかを理解することは、開発者にとってより効率的なコードの作成とパフォーマンスのボトルネックと最適化戦略の理解に役立つためです。 1)エンジンのワークフローには、3つの段階が含まれます。解析、コンパイル、実行。 2)実行プロセス中、エンジンはインラインキャッシュや非表示クラスなどの動的最適化を実行します。 3)ベストプラクティスには、グローバル変数の避け、ループの最適化、constとletsの使用、閉鎖の過度の使用の回避が含まれます。

Pythonは、スムーズな学習曲線と簡潔な構文を備えた初心者により適しています。 JavaScriptは、急な学習曲線と柔軟な構文を備えたフロントエンド開発に適しています。 1。Python構文は直感的で、データサイエンスやバックエンド開発に適しています。 2。JavaScriptは柔軟で、フロントエンドおよびサーバー側のプログラミングで広く使用されています。


ホットAIツール

Undresser.AI Undress
リアルなヌード写真を作成する AI 搭載アプリ

AI Clothes Remover
写真から衣服を削除するオンライン AI ツール。

Undress AI Tool
脱衣画像を無料で

Clothoff.io
AI衣類リムーバー

Video Face Swap
完全無料の AI 顔交換ツールを使用して、あらゆるビデオの顔を簡単に交換できます。

人気の記事

ホットツール

EditPlus 中国語クラック版
サイズが小さく、構文の強調表示、コード プロンプト機能はサポートされていません

ZendStudio 13.5.1 Mac
強力な PHP 統合開発環境

DVWA
Damn Vulnerable Web App (DVWA) は、非常に脆弱な PHP/MySQL Web アプリケーションです。その主な目的は、セキュリティ専門家が法的環境でスキルとツールをテストするのに役立ち、Web 開発者が Web アプリケーションを保護するプロセスをより深く理解できるようにし、教師/生徒が教室環境で Web アプリケーションを教え/学習できるようにすることです。安全。 DVWA の目標は、シンプルでわかりやすいインターフェイスを通じて、さまざまな難易度で最も一般的な Web 脆弱性のいくつかを実践することです。このソフトウェアは、

MantisBT
Mantis は、製品の欠陥追跡を支援するために設計された、導入が簡単な Web ベースの欠陥追跡ツールです。 PHP、MySQL、Web サーバーが必要です。デモおよびホスティング サービスをチェックしてください。

mPDF
mPDF は、UTF-8 でエンコードされた HTML から PDF ファイルを生成できる PHP ライブラリです。オリジナルの作者である Ian Back は、Web サイトから「オンザフライ」で PDF ファイルを出力し、さまざまな言語を処理するために mPDF を作成しました。 HTML2FPDF などのオリジナルのスクリプトよりも遅く、Unicode フォントを使用すると生成されるファイルが大きくなりますが、CSS スタイルなどをサポートし、多くの機能強化が施されています。 RTL (アラビア語とヘブライ語) や CJK (中国語、日本語、韓国語) を含むほぼすべての言語をサポートします。ネストされたブロックレベル要素 (P、DIV など) をサポートします。
