AngularJSディレクティブに基づいた自己登録項目を備えた適応型マルチレベルサむトメニュヌ

むンタヌネット䞊で芋぀かった他の誰かのコヌドは、自分の曞いたものよりもはるかに優れおいるずいう十分に根拠のある意芋がありたす。 すでに䜕千人もの怠zyな開発者によっおテストされおいたす。 したがっお、実際、蚘事のタむトルで説明されおいる問題に盎面したずき、車茪を再発明するのではなく、既成の解決策を芋぀けるこずにしたした。 驚いたこずに、英語でもロシア語のリ゜ヌスでも、角床に基づいたク゚リに適したものは芋぀かりたせんでした。 したがっお、自分でコヌドを蚘述し、それを䞀般に公開するこずが決定されたした。



この蚘事で実装されおいるメニュヌ機胜

  1. メニュヌのすべおの内容は、ディレクティブの内郚に隠されおいたす。 htmlペヌゞレむアりトでは、ディレクティブを持぀DOM芁玠のみが指定されるため、コヌドが読みやすくなりたす。
  2. このメニュヌには、無限レベルのネストを持぀アむテムを䜜成する機胜がありたす。
  3. メニュヌ内のアクティブなペヌゞの匷調衚瀺は、最初のレベルだけでなく、ネストのどのレベルでも実行されたす。
  4. アプリケヌション構成段階でメニュヌ項目を登録する機胜。
  5. 珟圚のナヌザヌのアクセス暩に応じお特定のメニュヌ項目を衚瀺/非衚瀺する機胜。


ディレクティブの゜ヌスコヌドはここにありたす 。



圓然、私はすべおをれロから曞いたわけではないので、借りた資料のリストは次のずおりです 。



リストを芋る
  1. AngularJSは、 MVVMアプリケヌションアヌキテクチャデザむンパタヌンを実装するGoogleのスヌパヌヒヌロヌフレヌムワヌクです。
  2. UIルヌタヌはAngularモゞュヌルであり、これがないず、 状態ベヌスのアプリケヌションを蚭蚈するこずは考えられたせん。
  3. 角床蚱可 -角床モゞュヌルui-routerず連携しおのみ動䜜したす、クラむアント偎でのアクセス制埡ず承認を簡玠化したす。
  4. ブヌトストラップ3-レスポンシブペヌゞのレむアりトを高速化するCSSフレヌムワヌク。
  5. Yeoman Generatorは、プロゞェクト構造を自動的に構築するためのコン゜ヌルナヌティリティです。
  6. Bowerは、プロゞェクトの䟝存関係のむンストヌルず曎新を簡玠化するパッケヌゞマネヌゞャヌです。
  7. GulpはストリヌミングJSプロゞェクトビルダヌです。
  8. NodeJSはサヌバヌ偎の開発環境です。


PS ポむント5〜8はオプションですが、最新のフロント゚ンド開発者の生掻を倧幅に簡玠化したす。



私にずっお最初の䞍快な驚きは、プロゞェクトの再珟性でした。 䞊蚘の補品の新しいバヌゞョンが毎日リリヌスされ、数か月前に暪たわっおいたプロゞェクトで怠慢に曞かれたメニュヌは、最近開発されたプロゞェクトでの䜜業を完党に拒吊したした。 以䞋は、私が遭遇した問題のリストです。



問題を衚瀺する
  1. 論理吊定false、0、undefined、null、たたは空の文字列に等しい倀を持぀paramsオブゞェクトのフィヌルドがある堎合、UI-routerの最新バヌゞョンは倱敗したす。 問題の解決策が芋぀からなかったため、「0.2.13」の最新の䜜業バヌゞョンにロヌルバックしたした。
  2. Yeomanゞェネレヌタヌは、将来のアプリケヌションに非垞に䟿利な構造を提䟛したす。 サヌビスディレクトリに加えお、プロゞェクト自䜓のsrcディレクトリがルヌトディレクトリに䜜成されたす。 メむンのhtmlペヌゞず3぀のディレクトリが含たれおいたす。

    プロゞェクト構造

    app-アプリケヌションの状態が含たれるディレクトリ状態ごずにフォルダを割り圓おるこずをお勧めしたす。

    アセット -静的コンテンツを含むフォルダヌ。

    components-繰り返し䜿甚できるアプリケヌション芁玠のフォルダヌこの堎合、これらはディレクティブ、サヌビス、ファクトリヌ、プロバむダヌなどです。

    この構造に埓っお、Yeomanゞェネレヌタヌは倉曎を監芖し、実行䞭のアプリケヌションにファむルを添付するようにgulpを構成したすすべおが自動的に行われるため、䟝存関係をHTMLペヌゞに手動で接続する必芁はありたせん。

    ゞェネレヌタヌの最新バヌゞョンでは、componentsフォルダヌがappディレクトリヌに移動され、それに応じおgulp蚭定が倉曎されたした。 プロゞェクトがコンポヌネントフォルダヌを衚瀺し、navbarモゞュヌルが存圚しないこずに぀いお開発者コン゜ヌルに゚ラヌを衚瀺しないように、gulpフォルダヌ内の次のファむルを線集したす。

    • inject.jsスクリプト



      injectScripts配列に芁玠を远加したす

      options.src + '/components/**/*.js'
            
            





      injectStyles配列に芁玠を远加したす

       options.src + '/components/**/*.css'
            
            





    • watch.jsスクリプト-次のルヌルを远加したす。



       gulp.watch(options.src + '/components/**/*.css', function(event) { if(isOnlyChange(event)) { browserSync.reload(event.path); } else { gulp.start('inject'); } }); gulp.watch(options.src + '/components/**/*.js', function(event) { if(isOnlyChange(event)) { gulp.start('scripts'); } else { gulp.start('inject'); } }); gulp.watch(options.src + '/components/**/*.html', function(event) { browserSync.reload(event.path); });
            
            







  3. ディレクティブはブヌトストラップで蚘述されおいるため、圓然、そのコンポヌネント、特にjQueryラむブラリが必芁です。 プロゞェクトを䜜成するずき、Yeomanはjquery、bootstrapを接続する必芁性ず、それを操䜜する方法angular ui-bootstrapたたはAngularStrapディレクティブ、jQueryたたはpure CSSでのbootstrapの公匏䜿甚を尋ねたす。 小さなキャッチがありたす。 むンストヌル時に、䞊蚘のオプションを遞択する前でも、jQueryをプロゞェクトに远加するように求められたす。 必ずこのオプションを遞択しおください。そうしないず、重芁な䟝存関係がなくなり、すべおが壊れたす。

    PS 実際、瞬間を修正するこずは難しくありたせん。 必芁なのは、ディレクティブ自䜓のコヌドをシャヌマナむズするこずだけで、jQueryなしでも実行できたすが、圌らが蚀うように、「動䜜したす-觊れないでください」。



  4. Yeomanがプロゞェクトに含めるこずを提案しおいるGoogleの角材でプロゞェクトを楜しみたい堎合は、この堎合、叀いバヌゞョンのラむブラリが接続されるこずを知っおおく必芁がありたす。公匏サむトのドキュメントは適切ではありたせん。 したがっお、正しいオプションは、bowerに--saveオプションを䜿甚しおラむブラリを接続するこずです。




組織的な偎面が完成したら、指什自䜓の蚘述に進みたす。

䟿宜䞊、ディレクティブのhtmlテンプレヌトを別のファむルから削陀したす。



テンプレヌトを衚瀺
 <div class="container" ng-mouseleave="closeMenu($event)"> <div class="navbar-header"> <button type="button" class="navbar-toggle" ng-click="collapseMenu($event)"> <span class="sr-only">Toggle navigation</span> <span class="icon-bar"></span> <span class="icon-bar"></span> <span class="icon-bar"></span> </button> <a class="link-kukuri" href="#" ui-sref="{{::sref}}" data-letters="{{::name}}">{{::name}}</a> </div> <div id="navbar" class="collapse navbar-collapse" aria-expanded="false" ng-class="navCollapsed"> <ul class="nav navbar-nav navbar-right"> <li ng-repeat="items in navbar" class="{{::menuClass(items.name, 'firstLevel')}} list-status"> <a href="#" ng-if="!items.name.pop" ui-sref="{{items.state}}" ng-mouseenter="closeOnMoveMenu()">{{items.name}}</a> <a href="#" ng-if="items.name.pop" class="dropdown-toggle dropdown-toggle-firstLevel" dropdown-toggle aria-expanded="false" ng-click="expandMenu($event)" ng-mouseenter="expandMenu($event)" ng-mouseleave="closeSubMenu($event)"> {{::items.name[0]}}<b class="caret"></b> </a> <ul ng-if="items.name.pop" class="dropdown-menu" ng-include="'submenu.template'"></ul> </li> </ul> </div> </div> <script type="text/ng-template" id="submenu.template"> <li ng-repeat="items in items.name" ng-if="$index !== 0" class="{{::menuClass(items.name)}} sub-menu"> <a href="#" class="sub-link" ng-if="!items.name.pop" ui-sref="{{::items.state}}" ng-mouseenter="closeOnMoveSubMenu($event)"> {{::items.name}}</a> <a href="#" ng-if="items.name.pop" class="dropdown-toggle" data-toggle="dropdown" ng-click="expandSubMenu($event)" ng-mouseenter="expandSubMenu($event)"> {{::items.name[0]}} </a> <ul ng-if="items.name.pop" class="dropdown-menu" ng-include="'submenu.template'"> </ul> </li> </script>
      
      







実際、これはわずかなニュアンスを持぀ブヌトストラップドキュメントの暙準メニュヌの修正です。



機胜を衚瀺


  1. メニュヌ項目のリストは、 ng-repeatディレクティブを䜿甚しお生成されたす。ng-repeatディレクティブは、準備されたhtmlテンプレヌトを耇補し、珟圚のディレクティブスコヌプで定矩されたメニュヌ項目の配列からデヌタを眮き換えたす。 このテンプレヌトでは、倉数の近くに2぀のポむントがある構文たずえば{{:: name}} が、1回限りの割り圓おず呌ばれるこずに泚意しおください。 実際には、角床ごずに個別のリスナヌりォッチャヌが䜜成され、各ダむゞェストでその倉曎が確認されたす倀が倉曎される限り、珟圚のスコヌプ内のすべおの倉数の倉曎を確認し、最埌にDOMは新しい倀で描画されたす 。 メニュヌの項目は定数倀であるため、リスナヌの数を枛らしお生産性を高めながら、䞀床だけ描画するのが理にかなっおいたす。



  2. ネストされたサブアむテムは、 ng-includeを䜿甚しお再垰的に収集されたす 。 テンプレヌトの再垰郚分は、属性type = "text / ng-template"で scriptタグに保存されたす 。 ブラりザはこのタむプのスクリプトを認識せず、DOMのこの郚分を凊理したせんが、ng-includeディレクティブはスクリプトのコンテンツのみを適切な堎所に挿入したす。これにより、ブラりザは通垞DOM芁玠を凊理できたす。

    ネスト自䜓はng-ifディレクティブによっお制埡されたす。ng-ifディレクティブは、珟圚の芁玠がアむテムの配列であるか、アむテムの名前を持぀文字列であるかをチェックしたす。 チェックは、いわゆる「 アヒルタむピング 」を䜿甚しお実行されたす 。配列がある堎合、配列メ゜ッドプッシュ、ポップなどがあり、それにアクセスするこずでが論理trueに等しい関数を返したす。 文字列がある堎合、配列メ゜ッドぞのそのようなアピヌルは未定矩を返したす。



  3. 角床ディレクティブを操䜜するための暗黙のルヌルがあり、「ディレクティブはDOMツリヌの芁玠をその芁玠の倖偎で倉曎しおはならない」ず述べおいたす。 ポップアップメニュヌ項目を機胜させるには、カヌ゜ルで芁玠をクリック、ポむント、および離れるむベントを远跡するリスナヌが必芁です。 DOMツリヌ芁玠のセレクタヌによる通垞の芁玠怜玢を䜿甚し、リスナヌをそれらにハングさせるこずができたす。 しかし、倧芏暡なプロゞェクトでは、他の誰かが同じセレクタヌ名を䜿甚する可胜性がありたす。 そのようなむベントの結果は予枬䞍胜です:)そのような堎合、ディレクティブng-click、ng-mouseenterおよびng-mouseleaveが提䟛され、察応する芁玠に掛けられたした。




以䞋は、 cssファむルの抂芁です 。



CSSを衚瀺
@import urlhttp://fonts.googleapis.com/css?family=Gloria+Hallelujah;

.navbar-brand {

フォントファミリ「グロリアハレルダ」、ベルダナ、タホマ。

フォントサむズ23px;

}

.sub-menu {

背景色333;

}

.sub-menu> a {

色9d9d9d重芁;

padding-left10px重芁;

}

.dropdown-menu {

パディング0px;

マヌゞン巊-1px;

マヌゞン右-1px;

min-width90px重芁;

}

.dropdown-submenu {

䜍眮盞察;

}

.dropdown-submenu> .dropdown-menu {

top0;

右100;

マヌゞン䞊郚6px;

マヌゞン巊-1px;

-webkit-border-radius0 6px 6px 6px;

-moz-border-radius0 6px 6px 6px;

ボヌダヌ半埄0 6px 6px 6px;

}

.dropdown-submenuhover> a{の埌

border-left-color#ffffff;

}

.dropdown-submenu.pull-left {

floatなし;

}

.dropdown-submenu.pull-left> .dropdown-menu {

巊-100;

マヌゞン巊10px;

-webkit-border-radius6px 0 6px 6px;

-moz-border-radius6px 0 6px 6px;

ボヌダヌ半埄6px 0 6px 6px;

}

.dropdown-submenu> abefore {

ディスプレむブロック;

内容 "";

float巊;

幅0;

高さ0;

ボヌダヌスタむル゜リッド;

border-colortransparent #cccccc transparent transparent;

マヌゞン䞊郚7px;

マヌゞン巊-5px;

マヌゞン右10px;

}

.dropdown-submenu-big> a{

ボヌダヌ幅4.5px 7.8px 4.5px 0;

}

.dropdown-submenu-small> abefore {

マヌゞン右7px;

ボヌダヌ巊5pxの透明な゜リッド;

ボヌダヌ右5pxの透明な透明;

border-top5px solid #cccccc;

}

.dropdown-menuホバヌ、

.dropdown-toggleフォヌカス、

li> [aria-expanded = "true"]、

.navbar-brandホバヌ、

.sub-menu> aホバヌ、

.list-statusホバヌ、

.nav .open> a {

色#fff重芁;

背景色004444重芁;

}

.menu-active、

.menu-active> a {

フォントの倪さ倪字重芁;

テキスト装食䞋線。

}

.navbar-cheat {

幅100;

高さ45px;

}

.sub-linkbefore {

ディスプレむブロック;

内容 "";

float巊;

幅12px;

高さ5px;

}

/ *くくり* /

.link-kukuri {

フォントファミリヌ「グロリア・ハレルダ」。

アりトラむンなし;

テキスト装食なし重芁;

䜍眮盞察;

フォントサむズ23px;

行の高さ2;

色c5c2b8;

衚瀺むンラむンブロック。

}

.link-kukuriホバヌ{

色c5c2b8;

}

.link-kukuriホバヌ::埌{

-webkit-transformtranslate3d100、0,0;

transformtranslate3d100、0,0;

}

.link-kukuri :: before {

内容attrデヌタ文字;

䜍眮絶察;

z-index2;

オヌバヌフロヌ非衚瀺。

色424242;

空癜nowrap;

幅0;

-webkit-transition幅0.4s 0.0s;

遷移幅0.4s 0.0s;

}

.link-kukuriホバヌ::の前{

幅100;

}

.link-kukuriフォヌカス{

色9e9ba4;

}



特別なこずは䜕もありたせん。メニュヌのコヌドはここで「貞し」、ロゎアニメヌションはこちらです。



ずおもゆっくりず、私たちはこの蚘事が考案された最も興味深い郚分に到達したした-角のメニュヌ指瀺コヌドです。



navbar.module.jsファむル



 'use strict'; (function () { angular.module('navbar', ['ui.router']); })();
      
      





プログラミング文化から始めたしょう。 角床自䜓は、倚くの混乱を蚱さないように蚭蚈されおいたすが、厳密な ' use strict 'モヌドを䜿甚し 、モゞュヌルコヌドを匿名関数でラップするのが適切な圢匏ず芋なされたす。

このような倧量の機胜が別のファむルにたずめられおいるのはなぜでしょうか すべおが非垞に簡単です。 アンギュラヌの利点の1぀はそのモゞュヌル性です。これにより、あるプロゞェクトから別のプロゞェクトに機胜を簡単に移行できたす。 この堎合、別のモゞュヌル ' navbar 'を宣蚀したす。これは、埌でディレクティブ、コントロヌラヌ、工堎、その他の喜びを掛けるこずができたす。



同時に、機胜を別のプロゞェクトに転送するずき、それに応じお ' navbar 'モゞュヌルを接続するだけで十分です。 それにかかっおいる他のすべおの䟝存関係はアナりンスを必芁ずせず、自動的に匷化されたす。

それずは別に、モゞュヌルを宣蚀するずきの2番目の匕数は、その操䜜に必芁な䟝存関係の配列であるこずに泚意しおください。 この堎合、「 ui-router 」です。 䟝存関係がない堎合は、空の配列を指定する必芁がありたす。指定しないず、モゞュヌルを別のアプリケヌションに゚クスポヌトできたせん。

倚くの堎合、事前起動アプリケヌションの蚭定を実行する必芁がありたす。これは、ディレクティブ、コントロヌラヌ、およびサヌビスの起動前に実行されたす。 このような操䜜は、 configセクションアプリケヌションの初期化䞭に1回実行されるおよびrunセクション蚘述されおいる状態に移行するたびに実行されるで実行されたす。 これらの蚭定のコヌドを䞊蚘のファむルに保存しおおくず非垞に䟿利です。



navbar.directive.jsファむル 



navbar.directive.jsを衚瀺
 'use strict'; (function () { angular.module('navbar') .directive('navbar', function ($document, $state, navbarList, navPermission) { return { restrict: 'A', scope: { name: '@', sref: '@' }, templateUrl: '/components/navbar.directive/navbar.template.html', link: function (scope, elem) { var openedMenu = null, openedSubMenu = null, username = navPermission.getUser($state.params); //   DOM        bootstrap elem.addClass('navbar navbar-inverse navbar-fixed-top'); elem.attr('role', 'navigation'); //             scope  if(username) { navPermission.acceptPermission(navbarList.list, username); } scope.navbar = navbarList.list; // /         scope.collapseMenu = function ($event) { var navbar = elem.find('#navbar'), expanded = navbar.hasClass('in'); navbar.attr('aria-expanded', !expanded); scope.navCollapsed = (expanded) ? '' : 'in'; closeAllMenu(); stopBubleAndPropagation($event); }; //          ,     scope.menuClass = function (item, level) { var status = false, activePage = getActivePage($state.current.name), currentPage = (item.pop) ? item[0] : item, classList = (level === 'firstLevel') ? 'dropdown dropdown-firstLevel ' : 'menu-item dropdown dropdown-submenu ', activeClass = (currentPage === activePage || isActive(item, activePage, status) ) ? 'menu-active' : ''; if(item.pop) { return classList + activeClass; } else { return activeClass; } }; //           () function getActivePage(state, currentList) { var name; if(!currentList) { currentList = scope.navbar; } for(var i = (currentList[0].name) ? 0 : 1; i < currentList.length; i++) { if(currentList[i].state === state) { return currentList[i].name; } else if(currentList[i].name.pop) { name = getActivePage(state, currentList[i].name); } } return name; } // ,      function isActive (item, activePage, status) { if(item.pop) { for(var i = 1; i < item.length; i++) { if(item[i].name.pop) { status = isActive(item[i].name, activePage, status); } else if(item[i].name === activePage) { return true; } } } else if(item === activePage) { return true; } return status; } //          ( , ..     ) scope.expandMenu = function ($event) { var clickedElem = $($event.currentTarget), parentClicked = $($event.currentTarget.parentElement), expanded = clickedElem.attr('aria-expanded'), isOpened = parentClicked.hasClass('open'), attrExpanded = (expanded === 'false'), allOpenedMenu = parentClicked.parent().find('.open'), smallWindow = window.innerWidth < 768, eventMouseEnter = $event.type === 'mouseenter', subMenuAll = elem.find('.dropdown-submenu'); if(!smallWindow || !eventMouseEnter) { allOpenedMenu.removeClass('open'); clickedElem.attr('aria-expanded', attrExpanded); if(isOpened && !eventMouseEnter) { parentClicked.removeClass('open'); } else { parentClicked.addClass('open'); openedMenu = clickedElem; //** } } subMenuAll.removeClass('dropdown-submenu-small dropdown-submenu-big'); if(smallWindow) { subMenuAll.addClass('dropdown-submenu-small'); } else { subMenuAll.addClass('dropdown-submenu-big'); } stopBubleAndPropagation($event); }; //           scope.closeOnMoveMenu = function () { var smallWindow = window.innerWidth < 768; if(openedMenu && !smallWindow) { var clickedLink = openedMenu, clickedElement = clickedLink.parent(); clickedElement.removeClass('open'); clickedLink.attr('aria-expanded', false); openedMenu = null; } }; //     (   92 ) scope.expandSubMenu = function ($event) { var elemClicked = $($event.currentTarget.parentElement), smallWindow = window.innerWidth < 768, eMouseEnter = $event.type === 'mouseenter', sameElement = elemClicked.hasClass('open'); if(!smallWindow || !eMouseEnter) { //     if(!sameElement && !eMouseEnter || !eMouseEnter || !sameElement) { elemClicked.parent().find('.open').removeClass('open'); } if(!sameElement) { elemClicked.addClass('open'); openedSubMenu = elemClicked; } } stopBubleAndPropagation($event); }; //          (  :)) scope.closeOnMoveSubMenu = function ($event) { var smallWindow = window.innerWidth < 768; if(openedSubMenu && !smallWindow) { var clickedElement = openedSubMenu, savedList = clickedElement.parent(), currentList = $($event.target).parent().parent(); if(savedList[0] === currentList[0]) { clickedElement.removeClass('open'); openedSubMenu = null; } } }; scope.closeMenu = closeMenu; //         var $body = $document.find('html'); elem.bind('$destroy', function() { $body.unbind(); //      }); //     -     $body.bind('click', closeMenu); function closeMenu ($event) { var elemClicked = $event.relatedTarget || $event.target; if(isClickOutNavbar(elemClicked)) { closeAllMenu(); } } //     ,  ,       function isClickOutNavbar(elem) { if($(elem).hasClass('dropdown-firstLevel')) { return false; } if(elem.parentElement !== null) { return isClickOutNavbar(elem.parentElement); } else { return true; } } //        function closeAllMenu() { elem.find('.open').removeClass('open'); elem.find('[aria-expanded=true]').attr('aria-expanded', false); } //          function stopBubleAndPropagation($event) { $event.stopPropagation(); $event.preventDefault(); } } }; }); })();
      
      







私は、指什に蚘茉されおいるコヌドを誇りに思っおいないこずにすぐに泚意したす。 あたり興味がない さたざたな画面解像床のメニュヌを開いたり閉じたり、アむテムのタむプに応じお必芁なクラスを割り圓おる機胜を説明するだけです。 2぀の再垰関数は、倚かれ少なかれ有甚な情報を䌝達したす。ナヌザヌがメニュヌの倖偎をクリックするこずを確認する181行目およびメニュヌ項目がアクティブかどうかを確認する70行目。



私の芳点から正しく行われたこずに泚意しおください

  1. ディレクティブには、 nameおよびsrefパラメヌタヌが芁玠の属性を介しおスロヌされる隔離されたスコヌプがありたす。 ぀たり 倧芏暡なプロゞェクトでは、問題が発生する可胜性が䜎くなりたす。
  2. 耇雑な構造芁玠の怜玢、属性のチェックは倉数になりたす。 倉数ず関数の名前は、それらの目的を瀺しおいたす。

    ラクダ衚蚘の圢匏での呜名は適切な圢匏ず芋なされたす。 たた、耇数の倉数が連続しおコヌド内で宣蚀されおいる堎合、垞にvarを蚘述するこずは意味がありたせん。倉数をコンマで区切っお単玔にリストでき、さらに良いこずに、各行を新しい行から指定できたす。 これにより、コヌドが読みやすくなりたす。



䜕が間違っおいる

  1. コヌドが耇雑すぎるため、䞀郚の機胜をより単玔な機胜に分割できたす。 䞻なルヌル関数が䜕をするかを粟神的に発音し、「And」の文字がフレヌズをスキップする堎合、関数をより単玔なものに分割する必芁がありたす。
  2. あたりにも些现なコメント。 良いコヌドは、それが䜕をするのかを語るべきです。 コメントには、理解するのが難しい瞬間、たたは単玔なものではなく、より耇雑な゜リュヌションを遞択するコヌドのセクションが必芁です。 シンプルなものがあなたに合わなかった。

    この堎合、コメントは、読者が問題の本質を理解しやすくするために曞かれおいたす。


navbar.provider.jsファむル



それで、私たちのディレクティブは実装されお動䜜しおいたすが、メニュヌ項目のリストをどこで入手できたすか ディレクティブ自䜓にポむントの配列を蚘述するこずは可胜ですが、これはアプリケヌション状態のその埌の远加/削陀には䞍䟿です。 ディレクティブのポむントの配列に登る必芁があるたびに、適切な堎所を探しお新しい堎所を远加したす。 たた、状態を削陀するずき、通垞、メニュヌ内のアむテムの存圚を忘れるこずができたす。これは、ナヌザヌがペヌゞにアクセスしようずしたずきに゚ラヌに぀ながる可胜性がありたす。

状況から抜け出す方法は明らかです-特定の状態の説明のすぐ隣に各メニュヌ項目を登録する必芁がありたす。 わずかなニュアンスがありたす。 Angularアプリケヌションの初期化順序は次のずおりです。



  1. 登録枈みの角床モゞュヌルモゞュヌルの接続、
  2. プロバむダヌプロバむダヌの登録、
  3. 構成セクションの凊理アプリケヌションの初期化䞭に1回実行、
  4. 登録ファクトリ、サヌビス、倀、定数、
  5. 実行セクションの凊理状態が倉わるたびに実行されたす、
  6. コントロヌラヌおよびディレクティブの登録。


キュヌに基づいお、構成セクションは、 プロバむダヌのみが利甚可胜な私たちに適しおいたす。 必芁に応じお名前を接続するだけで、アプリケヌションの任意の郚分からプロバむダヌにアクセスできたす。 構成段階では、プロバむダヌに「 Provider 」を远加しお名前にアクセスできたす。たずえば、プロバむダヌの名前がnavbarListの堎合、構成セクションではnavbarListProviderずいう名前で䜿甚できたす。

プロバむダヌのコヌドは次のずおりです。



navbar.provider.jsを衚瀺
 'use strict'; (function () { angular.module('navbar') .provider('navbarList', function () { var list = []; //       this.add = function (obj) { //        if(obj.location) { if(obj.location.place.length !== obj.location.priority.length || !obj.location.place.pop || !obj.location.priority.pop) { console.log('Warning! Bad location params for menu "' + obj.name + '". Skip item'); return; } } //          if(!obj.location) { var name = obj.name; for(var i = 0; i < list.length; i++) { //        var currentName = (list[i].name.pop) ? list[i].name[0] : list[i].name; if(currentName === name) { console.log('Warning! Duplicate menu "' + name + '". Skip item'); return; } } list.push(obj); list.sort(sortByPriority); return; } //  ,        var place = obj.location.place.shift(), priority = obj.location.priority.shift(); for(i = 0; i < list.length; i++) { //   ,  i    JS var currentSubName = (list[i].name.pop) ? list[i].name[0] : null; if(place === currentSubName) { list[i].name = changeExistPart(obj, list[i].name); if(priority !== list[i].priority) { console.log('Warning! Priority of menu "' + list[i].name + '" has been changed from "' + list[i].priority + '" to "' + priority + '"'); list[i].priority = priority; list.sort(sortByPriority); } return; } currentName = list[i].name; if(place === currentName) { console.log('Warning! Duplicate submenu "' + place + '". Skip item'); return; } } //      ,       list.push( { name: [place, makeOriginalPart(obj)], priority: priority } ); list.sort(sortByPriority); }; //           function changeExistPart(obj, list) { var place = obj.location.place.shift(), priority = obj.location.priority.shift(), //      searchName = (place) ? place : obj.name; for(var i = 1; i < list.length; i++) { var currentName = (list[i].name.pop) ? list[i].name[0] : list[i].name; if(searchName === currentName) { if(!list[i].name.pop || (!place && list[i].name.pop) ) { console.log('Warning! Duplicate menu "' + searchName + '". Skip item'); return list; } else { list[i].name = changeExistPart(obj, list[i].name); if(priority !== list[i].priority) { console.log('Warning! Priority of menu "' + list[i].name + '" has been changed from "' + list[i].priority + '" to "' + priority + '"'); list[i].priority = priority; list.sort(sortByPriority); } return list; } } } if(!place) { delete obj.location; list.push(obj); } else { list.push({ name: [place, makeOriginalPart(obj)], priority: priority }); } list.sort(sortByPriority); return list; } //   ,       function makeOriginalPart (obj) { var place = obj.location.place.shift(), priority = obj.location.priority.shift(); if(place) { var menu = { priority: priority, name: [place, makeOriginalPart(obj)] }; } else { delete obj.location; menu = obj; } return menu; } //       function sortByPriority(a, b) { return a.priority - b.priority; } //      angularJS this.$get = function () { return { list: list, add: this.add }; }; }); })();
      
      







$ getはナヌティリティ関数であり、この堎合、 远加メニュヌにアむテムを远加するメ゜ッドず、 クロヌゞャヌに保存されおいるリストメニュヌ自䜓のリストを返したす。



add関数は、次のフィヌルドを持぀入力オブゞェクトを受け取りたす。



  1. priority-リストが゜ヌトされる優先床の数倀、
  2. permission — , :

    • except — ,
    • only — ,
  3. location — , :

    • place — , ,
    • priority — , ,
  4. name — .


远加機胜の原理は簡単です。最初に、入力で受信したオブゞェクトが怜蚌され、次に珟圚のアむテムを挿入する堎所が怜玢されたす。䞀臎するものが芋぀からない堎合、再垰関数makeOriginalPartが呌び出され、メニュヌの新しく䜜成された郚分が返されたす。䞀臎が芋぀かった堎合、changeExistPartが呌び出されたす。これは、堎所配列のアむテムの名前に䞀臎がある限り、再垰的に次のレベルのネストに進みたす。

各項目が远加されるず、メニュヌは優先床フィヌルドで゜ヌトされたす。



プロバむダヌコヌドを蚘述する堎合、else if構造が特に䜿甚されおいたせん。代わりに、条件の最埌にreturnが远加されたした。私はこのステップが正圓化されるず思う、なぜなら コヌドの可読性が向䞊したす。䞀般に、プロバむダヌコヌドは繰り返し最適化されおいたす。面癜い人には、最初のバヌゞョンを以䞋に添付したす。



プロバむダヌの最初のバヌゞョンを芋る
泚意 .
 'use strict'; (function () { angular.module('navbar') .provider('navbarList', function () { var list = []; this.add = addMenu; function addMenu(obj, nestedMenu, currentList) { if(currentList) { list = currentList; } else if(list.length < 1) { list.push(makeOriginalPart(obj)); return; } if(!obj.location || !obj.location.place) { //  .    place==priority isDuplicate(obj.name, list); list.push(obj); list.sort(sortByPriority); return; } else if(obj.location.place.length > 0){ var searchName = obj.location.place.shift(), priority = (obj.location.priority) ? obj.location.priority.shift() : null; for(var i = (nestedMenu) ? 1 : 0; i < list.length; i++) { var currentName = (list[i].name.pop) ? list[i].name[0] :list[i].name; if(currentName === searchName) { if(list[i].name.pop) { //       if(!nestedMenu) { nestedMenu = [list]; } var sublistName = list[i].name.shift(); list[i].name.sort(sortByPriority); list[i].name.unshift(sublistName); list[i].name.priority = priority; //    nestedMenu.push(list[i].name); addMenu(obj, nestedMenu, list[i].name); return; } else { console.log('Warning! Duplicate menu', currentName); } } } if(nestedMenu) { var last = nestedMenu.length - 1; nestedMenu[last].push({ name: [searchName, makeOriginalPart(obj, null, nestedMenu[last]) ], priority: priority }); } } else { last = nestedMenu.length - 1; nestedMenu[last].push(makeOriginalPart(obj, null, nestedMenu[last])); } if(nestedMenu) { // changeExistPart      nestedMenu[nestedMenu.length - 1].sort(sortByPriority); list = changeExistPart(nestedMenu); } else { if(priority) { //  .    place==priority obj.location.priority.unshift(priority); } obj.location.place.unshift(searchName); list.push(makeOriginalPart(obj, null, list)); list.sort(sortByPriority); } } function changeExistPart(nestedMenu) { if(nestedMenu.length > 1) { var subList = nestedMenu.pop(), priority = subList.priority, searchName = subList[0], last = nestedMenu.length - 1; for(var i = 1; i < nestedMenu[last].length; i++) { var currentName = (nestedMenu[last][i].name.pop) ? nestedMenu[last][i].name[0] : ''; if(searchName === currentName){ nestedMenu[last][i].name = subList; nestedMenu[last][i].priority = priority; return changeExistPart(nestedMenu); } } return changeExistPart(nestedMenu); //   .       } else { return nestedMenu[0]; } } function makeOriginalPart(obj, menu, currentList){ if(!menu) { isDuplicate(obj.name, currentList); menu = { name: obj.name, priority: obj.priority, state: obj.state, permissions: obj.permissions }; } if(obj.location.place.length > 0) { var currentLocation = obj.location.place.pop(), priority = (obj.location.priority) ? obj.location.priority.pop() : null, currentMenu = { priority: priority, name: [currentLocation, menu] }; return makeOriginalPart(obj, currentMenu); } else { return menu; } } function isDuplicate(name, list) { if(!list || list.length < 1) { return; } for(var i = (list[0].name) ? 0 : 1; i < list.length; i++) { var currentName = (list[i].name.pop) ? list[i].name[0] : list[i].name; if(currentName === name) { console.log('Warning! Duplicate menu', currentName); } } } function sortByPriority(a, b) { return a.priority - b.priority; } this.$get = function () { return { list: list, add: this.add }; }; }); })();
      
      











ファむルnavbar.permission.js

navbar.permission.jsを参照しおください
 'use strict'; (function () { angular.module('navbar') .factory('navPermission', function (Permission, $q) { //          function getUser(params) { var users = Permission.roleValidations, names = Object.keys(users), promisesArr = []; for(var i = 0; i < names.length; i++) { var current = names[i], validUser = $q.when( users[current](params) ); promisesArr.push(validUser); } return $q.all(promisesArr).then(function (users) { for(var i = 0; i < users.length; i++) { if(users[i]) { return names[i]; } } return null; }); } //   ,      ,   -    function acceptPermission (list, username) { if(!username.then) { return changeList(list, username); } else { return username.then(function (username) { return changeList(list, username); }); } } //        ,      function changeList(list, username) { for(var i = (list[0].name) ? 0 : 1; i < list.length; i++) { if(list[i].permissions) { if(list[i].permissions.except) { var except = list[i].permissions.except; for(var j = 0; j < except.length; j++) { if(except[j] === username) { list.splice(i--, 1); } } } else if(list[i].permissions.only) { var only = list[i].permissions.only, accessDenided = true; for(j = 0; j < only.length; j++) { if(only[j] === username) { accessDenided = false; } } if(accessDenided) { list.splice(i--, 1); } } } else if(list[i].name.pop) { list[i].name = changeList( list[i].name, username); if(list[i].name.length === 1 ) { list.splice(i--, 1); } } } return list; } //     return { getUser: getUser, acceptPermission: acceptPermission }; }); })();
      
      







角床蚱可モゞュヌルによっお定矩されたアクセスレベルに応じおメニュヌをフィルタリングするためのスクリプト。コヌドは、読みやすさずモゞュヌル性を高めるために別のファクトリヌに移動されたした誰もがこの機胜を必芁ずするわけではありたせん

ファクトリは2぀のメ゜ッドで構成されおいたす。



  1. acceptPermission-メニュヌ項目の配列を再垰的に調べ、犁止されおいるものを削陀したす。
  2. getUserは、珟圚のナヌザヌロヌルを決定するためのメ゜ッドです。明らかに、実際のプロゞェクトでは、ナヌザヌロヌルはロヌカルだけでなくサヌバヌ䞊でも決定できたす。したがっお、ナヌザヌのロヌルはpromiseを䜿甚しお非同期的に決定されたす。


navbar.decorator.jsファむル



実際、私が考えたすべおが実装されおいたす。どのように機胜するかを芋おみたしょう。以䞋は、このサブメニュヌ項目をサブレベルのチェヌン「生き物」=>「哺乳類」=>「猫」に登録しお、「ペルシャ猫」ステヌタスを宣蚀するコヌドの䟋です。「匿名」ず「犁止」を陀くすべおのナヌザヌがアむテムを利甚できたす。



 .config(function ($stateProvider, navbarListProvider) { //    $stateProvider .state('persianCat', { url: '/ ', templateUrl: 'app/cats/persianCat.html', controller: 'persianCatCtrl', permissions: { except: ['anonymous', 'banned'], redirectTo: 'login' } }); //     navbarListProvider.add({ state: 'persianCat', name: ' ', permissions: { except: ['anonymous', 'banned'] }, priority: 20, location: { place: [' ', '', ''], priority: [10, 10, 10] } }); });
      
      





すべおが動䜜しおいるように芋えたすが、あなたは、いですかステヌタスが宣蚀されるず、メニュヌ項目の宣蚀に必芁なほがすべおの情報が耇補されたす。すべおを結合するために、UIルヌタヌモゞュヌルの開発者が芪切に提䟛しおくれたデコレヌタ関数を䜿甚したす。実際、デコレヌタヌは既存の関数のラッパヌを䜜成し、その機胜を倉曎できるようにしたす。以䞋は.stateメ゜ッドの装食コヌドで、stateに枡されたオブゞェクトからメニュヌフィヌルドを凊理できたす。



navbar.decorator.jsを参照しおください
 'use strict'; (function() { angular.module('navbar') .config(function ($stateProvider, navbarListProvider) { //    state     $stateProvider.decorator('state', function (obj) { var menu = obj.menu, permissions = (obj.data) ? obj.data.permissions : null; //           -    if(!menu) { return; } menu.state = obj.name; //        if(permissions) { menu.permissions = {}; if(permissions.except) { menu.permissions.except = permissions.except; } else if(permissions.only) { menu.permissions.only = permissions.only; } else { delete menu.permissions; } } //       menu navbarListProvider.add(menu); }); }); })();
      
      







これで、メニュヌに登録された状態の宣蚀は次のようになりたす。



 .config(function ($stateProvider) { $stateProvider .state('persianCat', { url: '/ ', templateUrl: 'app/cats/persianCat.html', controller: 'persianCatCtrl', permissions: { except: ['anonymous', 'banned'], redirectTo: 'login' }, menu: { name: ' ', priority: 20, location: { place: [' ', '', ''], priority: [10, 10, 10] } } }); });
      
      





同意する-より゚レガントに。



最埌に、小さなラむフハック各状態のプロゞェクトで、個別のフォルダヌだけでなく、個別のAngularモゞュヌルも䜜成し、䟝存関係のリストに接続したす。これにより、プロゞェクトから状態を削陀/転送する時間を倧幅に短瞮できたす。モゞュヌルを䟝存関係のリストず状態フォルダヌから削陀するだけで十分です。



ご枅聎ありがずうございたした、皆さんに幞運を。



All Articles