I have next code:
#foreach (var offer in Model.Packages)
{
#Html.Partial("Search/PackageOffer", new Primera.Site.WebUI.Models.ViewModels.Search.PackageOfferViewModel
{
Package = offer,
DisplayPricePerPerson = Model.DisplayPricePerPerson,
RoomsCount = Model.RoomsCount
})
}
I need to implement infinite scroll using js. How can I call render partial view on js and pass parameters on it?
As a very basic demo, you want to have a Partial View that is returning some sort of html to you. In my case this is just the next 10 numbers based on the the number submitted:
public IActionResult _InfiniteNumbers(int lastId)
{
var Ids = Enumerable.Range(lastId, 10);
return PartialView(Ids);
}
In a real world scenario this would be the next n entities of whatever - blogs, orderitems, comments.
The view for this is fairly straight forward as well:
#model IEnumerable<int>
#foreach (var number in Model)
{
<li data-number="#number">#number</li>
}
it just renders every number as a list item.
My main view then looks like this:
<h1>InfiniteNumbers</h1>
<ul id="partial-view-container">
</ul>
<input id="btnLoadMore" type="button" name="loadMore" value="Load More numbers" />
#section scripts {
<script>
$(document).ready(() => {
$("#btnLoadMore").click(() => { //the trigger
var ul = $("#partial-view-container"); //our container for all partial views
let lastId = 0; //the lastId displayed
let lastLi = $("#partial-view-container li:last-child"); //find the last number
if (lastLi.length !== 0) {
lastId = lastLi.attr("data-number"); //if we found something set the value
//lastId = +lastLi.data("number");
}
$.get(`/Home/_InfiniteNumbers?lastId=${lastId}`) //call our action method
.then((res) => {
ul.append(res); //append the html to the container
});
});
});
</script>
}
We have a <ul></ul> element that serves as our container for infinite loading. Since I am lazy, I am using a button as the trigger. In a more complex scenario, this would be an event waiting for the scrollbar to reach the bottom of the page or something similar. I'll leave this up to you.
We then query the list for its last <li> item and get its data-number attribute.
After this, we query our action method and get the next 10 items based on that number and finally just inject them into our view.
Related
I have a side drawer where I'm showing the current cart products selected by the user. Initially, I have a <p> tag saying the cart is empty. However, I want to remove it if the cart has items inside. I'm using an OOP approach to design this page. See below the class I'm working with.
I tried to use an if statement to condition the <p> tag but this seems the wrong approach. Anyone has a better way to do this. See screenshot of the cart in the UI and code below:
class SideCartDrawer {
cartProducts = [];
constructor() {
this.productInCartEl = document.getElementById('item-cart-template');
}
addToCart(product) {
const updatedProducts = [...this.cartProducts];
updatedProducts.push(product);
this.cartProducts = updatedProducts;
this.renderCart();
}
renderCart() {
const cartListHook = document.getElementById('cart-items-list');
let cartEl = null;
if (this.cartProducts.length === 0) {
cartEl = '<h2>You Cart is Empty</h2>';
} else {
const productInCartElTemplate = document.importNode(
this.productInCartEl.content,
true
);
cartEl = productInCartElTemplate.querySelector('.cart-item');
for (let productInCart of this.cartProducts) {
cartEl.querySelector('h3').textContent = productInCart.productName;
cartEl.querySelector('p').textContent = `£ ${productInCart.price}`;
cartEl.querySelector('span').textContent = 1;
}
}
cartListHook.append(cartEl);
}
}
By the way, the <p> should reappear if the cart is back to empty :) !
With how your code is setup, you would want to reset the list on each render. You would do this by totally clearing out #cart-items-list. Here is a deletion method from this question
while (cartListHook.firstChild) {
cartListHook.removeChild(cartListHook.lastChild);
}
But you could use any method to delete the children of an HTML Node. To reiterate, you would put this right after getting the element by its id.
P.S. You probably want to put more code into your for loop, because it seems like it will only create cart-item element even if there are multiple items in this.cartProducts.
I am using ng2-dragula for drag and drop feature. I am seeing issue when I drag and drop first element(or any element) at the end and then try to add new item to the array using addNewItem button, new item is not getting added to the end. If i don't drop element to the end, new item is getting added at the end in UI.
I want new items to be displayed at the bottom in any scenario. Any help is appreciated.
This issue is not reproducible with Angular 7. I see this happening with Angular 9
JS
export class SampleComponent {
items = ['Candlestick','Dagger','Revolver','Rope','Pipe','Wrench'];
constructor(private dragulaService: DragulaService) {
dragulaService.createGroup("bag-items", {
removeOnSpill: false
});
}
public addNewItem() {
this.items.push('New Item');
}
}
HTML
<div class="container" [dragula]='"bag-items"' [(dragulaModel)]='items'>
<div *ngFor="let item of items">{{ item }}</div>
</div>
<button id="addNewItem" (click)="addNewItem()">Add New Item
I edited the stackblitz from the comment to help visualize the issue. This seems to be triggered when a unit is dragged to the bottom of the list. Updated stackblitz : https://stackblitz.com/edit/ng2-dragula-base-ykm8fz?file=src/app/app.component.html
ItemsAddedOutOfOrder
You can try to restore old item position on drop.
constructor(private dragulaService: DragulaService) {
this.subscription = this.dragulaService.drop().subscribe(({ name }) => {
this.dragulaService.find(name).drake.cancel(true);
});
}
Forked Stackblitz
Explanation
There is some difference between how Ivy and ViewEngine insert ViewRef at specific index. They relay on different beforeNode
Ivy always returns ViewContainer host(Comment node)ref if we add item to the end:
export function getBeforeNodeForView(viewIndexInContainer: number, lContainer: LContainer): RNode|
null {
const nextViewIndex = CONTAINER_HEADER_OFFSET + viewIndexInContainer + 1;
if (nextViewIndex < lContainer.length) {
const lView = lContainer[nextViewIndex] as LView;
const firstTNodeOfView = lView[TVIEW].firstChild;
if (firstTNodeOfView !== null) {
return getFirstNativeNode(lView, firstTNodeOfView);
}
}
return lContainer[NATIVE]; <============================= this one
}
ViewEngine returns last rendered node(last <li/> element)ref
function renderAttachEmbeddedView(
elementData: ElementData, prevView: ViewData|null, view: ViewData) {
const prevRenderNode =
prevView ? renderNode(prevView, prevView.def.lastRenderRootNode!) : elementData.renderElement;
...
}
The solution might be reverting the dragged element back to original container so that we can let built-in ngForOf Angular directive to do its smart diffing.
Btw, the same technique is used in Angular material DragDropModule. It remembers position of dragging element and after we drop item it inserts it at its old position in the DOM which is IMPORTANT.
I am trying to build an SAPUI5 application using TreeTable and I'm facing some problems to use its methods.
In my app, I have a button which triggers this method.
onChangeViewContext: function(oEvent) {
.........
.........
var aViewContext = oContext.oModel.getProperty(sPath + "/ViewContext");
var aDataModel = oContext.oModel.getProperty("/ApplicationCollection/" + sAppId + "/DataModel");
var oStructure = this._createParentChildStructure(aDataModel);
var oTreeModel = this.getView().getModel("treeModel");
oTreeModel.setData(oStructure);
this._oViewDetailLine = oSource.getParent().getParent().getParent();
this._oViewDetailLine.setVisible(false);
this.byId("idSelectElementsPanel").setVisible(true);
this._setSelectedItems(aViewContext, oTree);
}
What I'm trying to do here is only bind the rows with my treeModel, get tree table object and send it to my _setSelectedItems method which below.
_setSelectedItems: function(aViewContext, oTree) {
oTree.clearSelection();
var sElementName;
var aSelectedIndices = [];
var aElements = [];
var aRows = oTree.getRows();
aRows.forEach(function(row) {
if (row._oNodeState !== undefined) {
aElements.push(row.getCells()[0].getText());
}
});
I need to get rows array here because I will use it for setting selected items of tree table. The problem is when "onChangeViewContext" triggered, oTable.getRows() returns an empty array. But when I click cancel button (which just hides my tree table, nothing more) and then trigger "onChangeViewContext" function again, I can get the rows array completely.
Even on the first call when I try to get table's model, I can get the treeModel and its data correctly.
I've tried to refresh bindings, aggregations etc. But no luck.
By the way, I'm using row binding in my xml view like this :
<t:TreeTable id="idSelectElementsTree" rows="{path: 'treeModel>/'}" selectionMode="MultiToggle" enableSelectAll="false"
rowSelectionChange="onSelectElement">
I'm really drowning here so any any help would be appreciated.
Edit : rest of the setSelectedIndexes function :
aViewContext.forEach(function(name) {
sElementName = name;
if (aElements.indexOf(sElementName) !== -1) {
aSelectedIndices.push(aElements.indexOf(sElementName));
}
});
aSelectedIndices.forEach(function(idx) {
if (oTree.getRows()[idx]._bHasChildren) {
oTree.expand(idx);
}
oTree.addSelectionInterval(idx, idx);
});
What could help here is to add an event rowsUpdated="onRowsUpdated" to the table in the XML view. This event is triggered after the table has been loaded and will hence provide you with the data via;
this.getView().byId("sTableId").getRows();
The difference to your approach is that the event would not be triggered by the press of a button but automatically, as the table is rendered. You can then also use this function to trigger another one as per your use case.
list.js Question:
How do you create a div so when you click it, it shows the last page of the pagination, after I have done a filter.
I have a list
var List = new List('list', {
valueNames: ['name'],
page: 5,
plugins: [ListPagination({})]
});
And say this list has 20 pages.
After I apply a filter:
List.filter(function(item) {
if (item.values().category.toLowerCase().indexOf('wordtofilter') > -1) {
return true;
} else {
return false;
}
});
It now has 5 pages. I want to have a button when I click it will take me to the last page.
Currently I can get to the last page of an unfiltered list using this:
$('.go-to-last-page').on('click', function(){
List.show(List.size(), 5);
});
But If i filter my list, and click it, it will attempt to take me to page 20, instead of 5. How do I make it so it takes me to the last page of the filtered list? (page 5)
I had this same problem a few years after your question, so I'm sharing my solution in case it might help someone else:
First I create the first and last page buttons:
<nav>
<button id="btn-first">FIRST</button>
<ul class="pagination"></ul>
<button id="btn-last">LAST</button>
</nav>
Secondly, in js I assign the data attributes that the paging buttons generated by list.js have by default.
const LIST_PAGINATION = 10;
var btn_first = document.getElementById('btn-first');
var btn_last = document.getElementById('btn-last');
btn_first.addEventListener("click",function(e){
btn_first.dataset.i = 1;
btn_first.dataset.page = LIST_PAGINATION;
},false);
btn_last.addEventListener("click",function(e){
let total = list.matchingItems.length; // list.js object in my case I called it "list"
let page = Math.ceil(total / LIST_PAGINATION);
btn_last.dataset.i = page;
btn_last.dataset.page = LIST_PAGINATION;
},false);
I have a view ( for creating a recipe ) to which I dynamically add a partial view ( representing products ). There can be several products added to recipe. Partial view is added on button click, using jQuery, and this works fine:
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
var $productsDiv = $('#productsDiv'),
url = $(this).data('url');
$.get(url, function (data) {
$productsDiv.append(data);
});
});
Partial view has a combo. Name of this combo is generated dynamically using Html helper extension method 'GetIndexedName' which adds unique index to specified name. If I click button twice it should render two partial views, after first click combo name should be "ProductsCombo0", after the second "ProductsCombo1"
#Html.DropDownList(
#Html.GetIndexedName("ProductsCombo"),
null,
htmlAttributes: new { #class = "form-control col-md-3" }
)
The problem is that the method #Html.GetIndexedName fires only after the first button click ( checked in debugger ). Next clicks only render partial view, but do use the method to generate name. All combos have name "ProductsCombo0", "ProductsCombo0"
Do you know how to make it fire everytime partial view is rendered?
If it can`t be done this way could you recommend me some other solution for generating unique ids?
Because every time you make the ajax call for a new row, It is a totally separate http call and this call does not have any information whether you are making the call for the first or eighth row. You need to store this value(which row you are making this call for) and use that when you build the dropdown name.
So first update your action method to accept a row index value.
public ActionResult AddRow(int id)
{
var vm = new AddItemVm {RowIndex = id};
vm.Items = new List<SelectListItem>()
{
new SelectListItem {Value = "1", Text = "IPhone"}
};
return PartialView("_NewItem", vm);
}
I am having small view model to pass the data between my action method and the partial view, which looks like,
public class AddItemVm
{
public int RowIndex { set; get; }
public List<SelectListItem> Items { set; get; }
}
And in the partial view, which is strongly typed to our view model, we will read the Model.RowIndex property and use that to build the dropdown name/id.
#model YourNamespaceHere.AddItemVm
<div>
#Html.DropDownList("ProductsCombo"+Model.RowIndex, Model.Items)
</div>
Now we need to make sure that we are sending a unique row index value to our AddRow method every time we want to add a new row. You can keep a javascript variable and increment it's value everytime user tries to add a new row and use that value.
$(function () {
var rowIndex = 0;
$('.loadPartial').on('click', function (evt) {
evt.preventDefault();
evt.stopPropagation();
rowIndex++;
var $productsDiv = $('#productsDiv'),
url = $(this).data('url') + '/' + rowIndex;
$.get(url, function (data) {
$productsDiv.append(data);
});
});
});
I finally found a solution to the problem. Data from AJAX call was cached and that is why function was called only once. All I had to do was to disable it. So I changed jQuery 'get' mehtod to 'post' which ( what i know ony now ) is not cached.