在假期休假之后,我有些打破了我之前建立的角色组件。
这是一个用于测试的JS小提琴的链接:https://jsfiddle.net/mvalli/nr4q0q0o/14/
由于某种原因,'items'
和'panel-format'
属性未将提供的值传递给它们之前的组件控件。我一直在努力找到我的错误并且可以使用第二组眼睛,因为它已经阻止我继续开发我正在添加的分页和排序功能。
注意:
在我尝试调试时,我添加了<div>products: {{ products }}</div>
,它显示了从ProductListController
传递的JSON数组产品,其中包含<list-panel items="..." panel-format="..."></list-panel>
标记从父视图。
我将变量products
应用到我$scope
的{{1}}变量后,也会在控制台中记录变量ProductListController
并获得成功结果。我尝试通过将ListController
和$scope.items
变量都记录到控制台来检查数据是否已传递到组件this.items
,结果为undefined
两者(正确的)变量应该是this.items
但我想检查$scope.items
,以防我在Angular中缺少具有隔离范围的概念。)
列表panel.module.js
var listPanelModule = angular.module("listPanel", []);
列表panel.component.js
angular.module("listPanel")
.component("listPanel", {
bindings: {
"items": "<",
"panelFormat" : "<"
},
controller: "ListController",
templateUrl: "js/modules/list-panel/list-panel.template.html"
});
用法:product-list.html
<div>products: {{ products }}</div>
<list-panel items='products' panel-format="listPanelFormat"></list-panel>
组件控制器:ListController.js
/*
* ListController.js
*
* Used to manage the list-component data attributes.
*
* @author mvalli
*/
console.log("Setting up the list-panel.controller.js...");
angular.module('listPanel')
.controller("ListController", ['$scope', '$filter', function($scope, $filter) {
// LOGGER
this.CLASSNAME = "ListController";
this.TESTING = true;
// CONSTANTS
console.log("onInit items: ", this.items);
console.log("onInit scope.items: ", $scope.items);
// PROPERTIES
// Filtering
this.query;
this.orderProp;
// Ordering
this.sort = {
sortingOrder : 'name',
reverse : false
};
// Item Tracking
this.pageSize = 10;
this.currentHeadRecord = 0;
this.currentTailRecord = this.currentHeadRecord + this.pageSize;
// Page Tracking
this.currentPage = 0;
this.lastPage = 0;
this.pagingButtonCount = 5;
// Data Structures
this.items;// JSON Object of the List Data/Content
this.filteredItems = [];
this.groupedItems = [];
this.pagedItems = [];
// Segmented View Button
this.selectListView = false;
this.selectGalleryView = true;
// FORMATTING
this.panelFormat; // JSON Object of the List Structure (i.e. Fields to display, order to display, any callbacks)
/*
* EXAMPLE - listData:
{
"_embedded" : {
"customers" : [
{ "firstName" : "Matt",
"lastName" : "Valli",
"age" : 29,
"_links" : {
self: "..."
}
},
{ "firstName" : "Paul",
"lastName" : "Kay",
"age" : 40
},
{ "firstName" : "Paul",
"lastName" : "Kay",
"age" : 40
}
]
}
}
* EXAMPLE - listFormat:
{
"headerLabel" : "Cloud Reseller",
"controller" : ServiceProviderController,
"templateUrl" : "js/modules/list-panel/list-panel.html",
"list" : {
"templateUrl": "views/serviceProvider/list-serviceProvider.html",
},
"gallery" : {
"templateUrl": "views/serviceProvider/gallery-serviceProvider.html",
}
"displayOrder" : [
{ "data" : _embedded.serviceProviders.name,
"label" : "Service Provider Name",
"link" : { "url": _embedded.cloudReseller._links.
"method": "GET" },
{ "data" : _embedded.customers.lastName,
"label": "Last Name"
"sortable" : true },
{ "data" : _embedded.customers.age
"label" : "Age"
"sortable" : true }
],
// BUTTON FEATURES
"rowControls" : [
{ "label": "Edit",
"templateUrl": "/controlButtons/edit",
"display" : true,
"link" : { _embedded.customers._link.self,
"method" : "GET,
route: "/serviceProvider/1/edit" }
"callback" : "" },
{ "label": "View Customers",
"display": true,
"link" : _embedded.customers._links.customers
"callback" : ""
}
]
"rowDeleteButton" : false,
"listExportable" : true
}
*
*/
// Methods
/**
* Changes the view in the List Panel to the List View.
*/
this.showListView = function() {
console.log(this.CLASSNAME + ".showListView()");
// Update Selected View
this.selectListView = true;
this.selectGalleryView = false;
console.log(this.CLASSNAME + ".selectListView: ", this.selectListView);
};
/**
* Changes the view in the List Panel to the Gallery View.
*/
this.showGalleryView = function() {
console.log(this.CLASSNAME + ".showGalleryView()");
// Update Selected View
this.selectListView = false;
this.selectGalleryView = true;
console.log(this.CLASSNAME + ".selectGalleryView: ", this.selectGalleryView);
};
/*
* Updates the Displays based on a change in using the controls.
*/
this.updateDisplay = function() {
console.log(this.CLASSNAME + ".updateDisplay()");
this.currentTailRecord = parseInt(this.currentHeadRecord) + parseInt(this.pageSize);
};
/**
* Uses the panelFormat to determine how many columns there are in the table.
*/
this.getTableWidth = function() {
console.log(this.CLASSNAME + ".getTableWidth()");
var width = 0;
if (this.panelFormat.displayColumns != null) {
width = this.panelFormat.displayColumns.length;
}
return width;
};
/**
* Returns true if an item matches the matcher in the collection,
* false if no item matches the matcher.
*
*/
var searchMatch = function (collection, matcher) {
if (!matcher) {
return true;
}
return collection.toLowerCase().indexOf(matcher.toLowerCase()) !== -1;
};
/**
* Lifecycle: init
*
* Sorts the items remaining after applying a search filter. An empty search
* filter sorts the complete collection of items.
*
*/
// init the filtered items
this.search = function () {
console.log("onSearch items: ", this.items);
// Creates an array of items that match the provided anonymous 'compareTo' function
this.filteredItems = $filter('filter')(this.items, function (item) {
// Loop through the attributes of an Item
for(var currentAttribute in item) {
// If the currentAttribute in the item matches the Search Query
if (searchMatch(item[currentAttribute], this.query))
// Reflect a Match
return true;
}
// Reflect a Mismatch
return false;
});
// Reorder the items based on the sorting order
if (this.sort.sortingOrder !== '') {
// Order the Filtered Items by the sortingOrder
this.filteredItems = $filter('orderBy')(this.filteredItems, this.sort.sortingOrder, this.sort.reverse);
}
// Ensure the user lands on the first page of the results
this.currentPage = 0;
// Generate the Pages based on the Panel Settings
this.groupToPages();
};
/**
* Creates a collection of sub-arrays sized by pageSize
*
*/
this.groupToPages = function () {
// Reset the pagedItems collection to an empty array to create a clean page grouping
this.pagedItems = [];
// Loop through all of the Filtered Items
for (var i = 0; i < this.filteredItems.length; i++) {
// If the current 'i' returns 0 from a modulus operator
if (i % this.pageSize === 0) {
// Create a new page/collection with the item and add it to the collection of pages
this.pagedItems[Math.floor(i / this.pageSize)] = [ this.filteredItems[i] ];
} else {
// Add the current item to an existing page collection
this.pagedItems[Math.floor(i / this.pageSize)].push(this.filteredItems[i]);
}
}
};
/**
*
* @param {type} numberOfPages - The length of the pagedItems.
* @param {type} start - The initial page button to display.
* @param {type} end - The last page button to display.
* @returns {Array} - An array of page buttons.
*/
this.range = function (numberOfPages, start, end) {
// Create a collection of Paging Buttons to return
var ret = [];
// Log the Range for Debugging
console.log(numberOfPages, start, end);
// If the number of pages is less than the size
if (numberOfPages < end) {
// The last button should equal the size
end = numberOfPages;
// The first button should
start = numberOfPages - $scope.gap;
}
for (var i = start; i < end; i++) {
// Add the buttons to the List of Buttons to display
ret.push(i);
}
// Log the Results for debugging
console.log(ret);
return ret;
};
/**
* Moves the currentPage back an index in the pagedItems collection if not
* on the first page.
*
*/
this.prevPage = function () {
if (this.currentPage > 0) {
this.currentPage--;
}
};
/**
* Moves the currentPage forward an index in the pagedItems collection if not
* on the last page.
*
*/
this.nextPage = function () {
if (this.currentPage < this.pagedItems.length - 1) {
this.currentPage++;
}
};
/**
* Sets the current page to the selected Paging Button.
*/
this.setPage = function () {
$scope.currentPage = this.n;
};
// Processes the Data on initialization
console.log("pre-search items: ", this.items);
console.log("scope.items: ", $scope.items);
this.search();
}]);
组件模板:list-panel.template.html
<!-- START DATATABLE EXPORT -->
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">{{ $ctrl.panelFormat.headerLabel }}</h3>
<div class="pull-right">
<div class="btn-group" role="group" aria-label="...">
<button type="button" class="btn btn-default" ng-click="$ctrl.showListView()" ng-selected="{{ $ctrl.selectListView }}"><span class="glyphicon glyphicon-list"></span>List</button>
<button type="button" class="btn btn-default" ng-click="$ctrl.showGalleryView()" ng-selected="{{ $ctrl.selectGalleryView }}"><span class="glyphicon glyphicon-th"></span>Gallery</button>
</div>
<div class="btn-group">
<button class="btn btn-danger dropdown-toggle" data-toggle="dropdown"><i class="fa fa-bars"></i> Export Data</button>
<ul class="dropdown-menu">
<li><a href="#" onClick ="$('#products').tableExport({type:'json',escape:'false'});"><img src='img/icons/json.png' width="24"/> JSON</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'json',escape:'false',ignoreColumn:'[2,3]'});"><img src='img/icons/json.png' width="24"/> JSON (ignoreColumn)</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'json',escape:'true'});"><img src='img/icons/json.png' width="24"/> JSON (with Escape)</a></li>
<li class="divider"></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'xml',escape:'false'});"><img src='img/icons/xml.png' width="24"/> XML</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'sql'});"><img src='img/icons/sql.png' width="24"/> SQL</a></li>
<li class="divider"></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'csv',escape:'false'});"><img src='img/icons/csv.png' width="24"/> CSV</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'txt',escape:'false'});"><img src='img/icons/txt.png' width="24"/> TXT</a></li>
<li class="divider"></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'excel',escape:'false'});"><img src='img/icons/xls.png' width="24"/> XLS</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'doc',escape:'false'});"><img src='img/icons/word.png' width="24"/> Word</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'powerpoint',escape:'false'});"><img src='img/icons/ppt.png' width="24"/> PowerPoint</a></li>
<li class="divider"></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'png',escape:'false'});"><img src='img/icons/png.png' width="24"/> PNG</a></li>
<li><a href="#" onClick ="$('#products').tableExport({type:'pdf',escape:'false'});"><img src='img/icons/pdf.png' width="24"/> PDF</a></li>
</ul>
</div>
</div>
</div>
<div class="panel-controls col-md-12" style="padding-top: 10px;">
<div class="form-group col-md-3 form-inline">
<label for="pageSize">Show</label>
<select id="pageSize" class="form-control" ng-model="$ctrl.pageSize" ng-change="$ctrl.updateDisplay()">
<option value="10">10</option>
<option value="25">25</option>
<option value="50">50</option>
<option value="100">100</option>
</select>
</div>
<label for="numberRecordsToDisplay">Entries</label>
<div class="form-group col-md-7">
<input id="searchProducts" class="form-control" placeholder="Search" ng-model="$ctrl.query">
</div>
<div class="form-group col-md-2">
<select id="sortProducts" class="form-control" ng-model="$ctrl.orderProp">
<option ng-selected="{{option.selected}}" value="{{option.id}}" ng-repeat="option in $ctrl.panelFormat.sortOptions">{{ option.name }}</option>
</select>
</div>
</div>
<div class="panel-body">
<div class="row">
<div id="list" ng-show="$ctrl.selectListView">
<div class="table-responsive col-md-12">
<table id="products" class="table table-striped table-hover">
<thead>
<tr>
<th custom-sort order="'name'" sort="sort">Name</th>
<th custom-sort order="'description'" sort="sort">Description</th>
<th custom-sort order="'licenseQty'" sort="sort">License Qty</th>
<th custom-sort order="'price.listPrice'" sort="sort">List Price</th>
<th custom-sort order="'price.discountPrice'" sort="sort">Discount Price</th>
<th custom-sort order="'savings'" sort="sort">Savings</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="listItem in $ctrl.items | filter: $ctrl.query | orderBy: $ctrl.orderProp">
<td>{{ listItem.name }}</td>
<td>{{ listItem.description }}</td>
<td>{{ listItem.licenseQty }}</td>
<td>{{ listItem.price.listPrice | currency }}</td>
<td>{{ listItem.price.discountPrice | currency }}</td>
<td>{{ calculateSavings(listItem) | currency }}</td>
</tr>
<tr ng-if="$ctrl.items == null || $ctrl.items.length == 0">
<td col-span="{{ $ctrl.getTableWidth() }}"><strong>No results found...</strong></td>
</tr>
</tbody>
</table>
</div>
</div>
<div id="gallery">
<div class="gallery" ng-show="$ctrl.selectGalleryView"><!-- gallery list -->
<!--<product-tile ng-repeat="product in $ctrl.products" data="product"></product-tile>-->
<a class="gallery-item {{ galleryItem.licenseQty }}" href="{{ $ctrl.route }}" ng-repeat="galleryItem in $ctrl.items | filter:$ctrl.query | orderBy:$ctrl.orderProp"><!-- gallery item -->
<div class="image"><!-- item image -->
<img src="assets/images/gallery/nature-1.jpg" alt="Nature Image 1">
<ul class="gallery-item-controls"><!-- item controls -->
<li><span class="gallery-item-remove"><i class="fa fa-times"></i></span></li><!-- controls item -->
</ul><!-- /item controls -->
</div><!-- ./item image -->
<div class="meta"><!-- item description -->
<strong>{{ galleryItem.name }}</strong>
<span>Licenses included</span>
<p><strong><ng-pluralize count="galleryItem.licenseQty" when="$ctrl.panelFormat.pluralize.licenses"></ng-pluralize></strong></p>
<span>Price</span>
<p>Cost Price: {{ galleryItem.price.costPrice | currency }}<br>
List Price: {{ galleryItem.price.listPrice | currency }}<br>
Discounted Price: {{ galleryItem.price.discountPrice | currency }}<br>
Total Savings: <span class="text-red">{{ calculateSavings(gallerItem) | currency }}</span>
</p>
<span>Description</span>
<p>{{ galleryItem.description }}</p>
</div><!-- ./item description -->
</a><!-- ./gallery item -->
<div ng-if="$ctrl.items == null || $ctrl.items.length == 0">
<strong>No results found...</strong>
</div>
</div><!-- ./gallery list -->
</div>
</div>
<div class="col-med-12">
<div class="col-md-6">
Showing {{ $ctrl.startingRecord }} to {{ $ctrl.currentLast }} of {{ $ctrl.dataList.length }} entries
</div>
<div class="col-md-6">
<nav class="pull-right" aria-label="Page navigation">
<ul class="pagination">
<li>
<a href="#" class="paginate_button previous" aria-label="Previous">
<span aria-hidden="true">Previous</span>
</a>
</li>
<li><a class="paginate_button" href="#">1</a></li>
<li><a class="paginate_button" href="#">{{ (parseInt($ctrl.data.length) / parseInt($ctrl.pageSize)) }}</a></li>
<li>
<a href="#" class="paginate_button next" aria-label="Next">
<span aria-hidden="true">Next</span>
</a>
</li>
</ul>
</nav>
</div>
</div>
</div><!-- ./panel-body -->
</div>
<!-- END DATATABLE EXPORT -->
产品的JSON示例:
{
"products": [
{
"id": 1,
"vendor" : 1,
"releaseDate": 1,
"price": 25.00,
"label" : "Microsoft Word",
"snippet" : "Document and Word Processing",
"description" : "Microsoft Word is the World-Leading Desktop Application for creating rich formatted documents.",
"licenseQty" : 1,
"contents" : []
},
{
"id": 2,
"vendor" : 1,
"releaseDate": 1,
"price": 25.00,
"label" : "Microsoft Excel",
"snippet" : "Spreadsheet Manager",
"description" : "Microsoft Excel is the World-Leading Spreadsheet Processing software for creating rich formatted documents.",
"licenseQty" : 1,
"contents" : []
},
{
"id": 3,
"vendor" : 1,
"releaseDate": 1,
"price": 25.00,
"label" : "Microsoft Powerpoint",
"snippet" : "Slideshow Presentation Manager",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 4,
"vendor" : 1,
"releaseDate": 1,
"price": 25.00,
"label" : "Microsoft Outlook",
"snippet" : "Email Manager",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 5,
"vendor" : 1,
"releaseDate": 2,
"price": 25.00,
"label" : "Microsoft OneNote",
"snippet" : "Cloud-based Document Manager",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 6,
"releaseDate": 5,
"price": 25.00,
"label" : "Microsoft Access",
"snippet" : "This Access Thing...",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 7,
"vendor" : 1,
"releaseDate": 2,
"price": 25.00,
"label" : "Microsoft Project",
"snippet" : "Project Management Tool",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 8,
"vendor" : 1,
"releaseDate": 3,
"price": 25.00,
"label" : "Microsoft Publisher",
"snippet" : "Content Layout Manager",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 9,
"vendor" : 1,
"releaseDate": 4,
"price": 25.00,
"label" : "Microsoft Visio",
"snippet" : "UML & Data Modeling Tool",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 10,
"vendor" : 1,
"releaseDate": 3,
"price": 25.00,
"label" : "Microsoft Sway",
"snippet" : "Side to Side",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 11,
"vendor" : 1,
"releaseDate": 5,
"price": 25.00,
"label" : "Microsoft Teams",
"snippet" : "Resource Manager",
"description" : "Blah, blah, blah",
"licenseQty" : 1,
"contents" : []
},
{
"id": 12,
"vendor" : 1,
"releaseDate": 1,
"price": 50.00,
"label" : "Microsoft Office 365",
"snippet" : "Lots of Apps!",
"description" : "This product includes 1 Licenses for Microsoft Office 365.",
"licenseQty" : 1,
"contents" : [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
]
},
{
"id": 13,
"vendor" : 1,
"releaseDate": 3,
"price": 150.00,
"label" : "Microsoft Office 365",
"snippet" : "Lots of Apps for All!",
"description" : "This product includes 5 Licenses for Microsoft Office 365.",
"licenseQty" : 5,
"contents" : [
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11
]
}
]
}