I am a student and learning angularJS for my project. Please bear with me for any silly questions.
I am trying to iterate through an array of challenges using ng-repeat[myChallenge in ListChallengesICreated.challenges]. In the mean time, each challenges, I am trying to call another function to pass an array of badge id, which return an array of badges URL's belongs to a that specific challenge.I also need to iterate through that badges URL's using ng-repeat[badge in filtered_badges] as well to display badges along each challenges.
But second ng-repeat only run after first ng-repeat is completed. So all the challenges display same badges pictures instead of it respective badges. Is there a way to run second ng-repeat concurrently while first being executed?
HTML code:
<table class="table table-condensed" style="border-style:solid; border-color:blue;width:100%" ng-init="list_challenges_I_created();">
<tr **ng-repeat="myChallenge in ListChallengesICreated.challenges" ** ng-init="get_badge(myChallenge.unlockRequiredBadges)">
<td class="cMName">{{myChallenge.name}}</td>
<td class="cMLocation" **ng-repeat="badge in filtered_badges" **>
<input type="image" ng-src="{{badge.imageURL}}" />
</td>
<td class="cMSDate">{{myChallenge.startDate}}</td>
<td class="cMEDate">{{myChallenge.endDate}}</td>
<td class="cMReview">
<div class="btn-group">
<button class="btn btn-warning" a data-target="#statsModal" role="button" data-toggle="modal">Stats</button>
<button class="btn btn-danger" a data-target="#tab2create">Edit</button>
</div>
</td>
<td class="cMPublish">nmfv</td>
<td class="cMShare">nmfnvm</td>
</tr>
</table>
JS File (which returns badges)
$scope.get_badge = function (allRequiredBadges) {
var path_badges = [];
var get_all_badges = {};
var all_badges = {};
var all_badges_URL = [];
$scope.filtered_badges = {};
path_badges = allRequiredBadges;
get_all_badges = $resource('/jsonapi/all_badges');
get_all_badges.get({}, function (response) {
all_badges = response;
for (var j = 0; j < path_badges.length; j++) {
for (var i = 0; i < all_badges.badges.length; i++) {
if (all_badges.badges[i].id == path_badges[j]) {
all_badges_URL.push(all_badges.badges[i]);
}
}
}
$scope.filtered_badges = all_badges_URL;
})
}
Actually the problem is not in ng-repeat directive but in incorrect scope usage. I think your $scope.get_badge function is defined in the controller and $scope used there is the controller's scope. So when get_badge called for 2 challenges then this line:
$scope.filtered_badges = all_badges_URL;
overwrites the previouly loaded badges, that's why you get the same badges for all challenges. The simplest solution is to but badges collection inside challenge object, like this:
In javascript:
myChallenge.filtered_badges = ...
In html:
<tr ng-repeat="myChallenge in ListChallengesICreated.challenges" ng-init="get_badge(myChallenge)">
<td class="cMLocation" ng-repeat="badge in myChallenge.filtered_badges" >
<input type="image" ng-src="{{badge.imageURL}}" />
</td>
Related
I tried to put viewbag data to javascript in order to fill the table without refreshing the whole page when modifying any data. And it is not working, but on html it's working .
Here's my code..
fillDatatable();
function fillDatatable() {
$('#TableRecords').html(
#if (Context.Session.GetString("cart") != null)
{
foreach (var item in ViewBag.cart)
{
<tr>
<td>
#item.Items.ItemID
</td>
<td>
#item.Items.ItemModelDescription
</td>
<td class="text-right">
<input id="#("UnitPrice" + item.Items.ItemID)" class="form-control text-right b-r-xl" value="#item.Items.ItemUnitPrice" oninput="return change_unitprice('#item.Items.ItemID')" />
</td>
<td class="text-right">
<input id="#("Quantity" + item.Items.ItemID)" class="form-control text-right b-r-xl" value="#item.Quantity" oninput="return change_quantity('#item.Items.ItemID')" />
</td>
<td class="text-right">
<label id="#("Subtotal" + item.Items.ItemID)">
#(item.Quantity * item.Items.ItemUnitPrice)
</label>
</td>
<td>
<a class="btn btn-sm btn-danger btn-rounded" asp-controller="purchaseorderheader" asp-action="Remove" asp-route-id="#item.Items.ItemID"><span class="fa fa-trash"></span></a>
</td>
</tr>
}
}
);
}
Where do am I wrong? Is there any way to make it right? any recommendation for better way is very much accepted. Thank you so much for your help.
You can get the ViewBag data in js like this:
var obj = #Html.Raw(Json.Serialize(ViewBag.cart));
Besides, you can not write the c# code in the js function. If you don't want to refresh the whole page, you can use ajax to request backend to get the updated data, then replace the data in orignal table with the returned data.
Update:
var purchaseOrderList = new List<trnPurchaseOrderLists>
{
new trnPurchaseOrderLists
{
PurchaseOrderID = "1",
Quantity = 10,
Items = new mfItems
{
ItemID = "1",
ItemModelDescription= "A"
}
},
new trnPurchaseOrderLists
{
PurchaseOrderID = "2",
Quantity = 11,
Items = new mfItems
{
ItemID = "2",
ItemModelDescription= "B"
}
},
};
ViewBag.purchaseOrder = purchaseOrderList;
JS
var obj = #Html.Raw(Json.Serialize(ViewBag.purchaseOrder));
console.log(obj);
Result:
could be your js is running before the document has uploaded? try putting your function into document.onload or window.onload (not sure which it's been a while since I've worked with js and web apps)
I currently have a form that will let the users to add item to the submission, since I am very new to KnockoutJS I just made this form to accept the one Product for the submission
<script type="text/html" id="page4-template">
<h4>Strain Information : </h4>
<table>
<tr>
<td class="firstCol">Stock number : </td>
<td><span id="SummaryP1_StockNum" data-bind="text: stockNumber"></span></td>
</tr>
<tr>
<td class="firstCol">Product Needed : </td>
<td>
<span id="SummaryP1_pdtNeeded" data-bind="text: pdtNeeded"></span>
<span data-bind="visible: pdtNeeded() == 'Other'">
<span id="SummaryP1_pdtNeededPleaseExplain" data-bind="text: pdtNeededPleaseExplain"></span>
</span>
</td>
</tr>
<tr>
<td class="firstCol">Requested Ship Date : </td>
<td><span id="SummaryP1_RequestedShipDate" data-bind="text: requestedShipDate"></span></td>
</tr>
<tr>
<td class="firstCol">Aditional Information : </td>
<td><span id="SummaryP1_AdditionalInformation" data-bind="text: additionalInformation"></span></td>
</tr>
</table>
<hr>
</script>
If I need to make this form to allow users to add more item to the submission dynamically, what should I be using here, I am little confused as thee are dynamic bootstrapping, Overservable Array and all. Can anyone please suggest what could I do to simple to allow users to dynamically add item.
I would suggest three steps:
The first step would be collect into one object all those observable properties which you bind to the table's elements:
createRowItem = function(data) {
return {
additionalInformation = ko.observable(data.additionalInformation),
pdtNeeded = ko.observable(data.pdtNeeded),
pdtNeededPleaseExplain = ko.obsevable(data.pdtNeededPleaseExplain),
requestedShipDate = ko.observable(data.requestedShipDate),
stockNumber = ko.observable(data.stockNumber),
}
};
You would obtain an instance of a new rowItem...
var newRowItem = createRowItem(data);
The second step is to create an observableArray (documentation) in your existing view-model:
self.rowItems = ko.observableArray([]);
To populate that array with your collection of rowItem instances you could call self.rowItems.push(newRowItem) (documentation) but it's more efficient to obtain a reference to the inner array (i.e., the primitive array which the observableArray is watching), add the new instance to that, then tell the observableArray that its data has been updated. [The reason for this efficiency has to do with the way Knockout works internally, and tracks mutations.]
My suggestion would be to do this inside a public function on your view-model:
self.addRowItem = function(newRowItem) {
var arr = ko.unwrap(self.rowItems); // obtain the underlying array
arr.push(newRowItem); // add the new object to the underlying array
self.rowItems.valueHasMutated(); // tell Knockout that the underlying array has been modified
};
The final step is to wrap your <tr> elements in a foreach binding (documentation):
<script type="text/html" id="page4-template">
<h4>Strain Information : </h4>
<table data-bind="foreach: rowItems">
<tr>
<td class="firstCol">Stock number : </td>
<td><span id="SummaryP1_StockNum" data-bind="text: stockNumber"></span></td>
</tr>
...
</table>
You will indeed want to use an observableArray to store multiple items. Then you loop through this array with the foreach binding and you add a method on your viewmodel to push new items to this array.
Something like this:
vm.row = ko.observableArray();
vm.addRow = function () {
vm.row.push({
stockNumber: ko.observable(1),
pdtNeeded: ko.observable('Other'),
pdtNeededPleaseExplain: ko.observable('Hello'),
requestedShipDate: ko.observable(),
additionalInformation: ko.observable()
})
}
Fiddle: https://jsfiddle.net/thebluenile/2q8tbp5n/
For good measure, I also added an example of how you could remove the rows.
How can I get access to the old Node and replace it?
My XHTML has this structure:
<table id="output">
<tr>
<td>Sometext</td>
<td>...</td>
<td>...<td>
</tr>
<tr class="CLASSNAME">
<td class="ElemKat">text</td>
<td class="ELEM">...</td>
<td class="EL">...</td>
</tr>
<tr class="CLASSNAME">
<td class="ElemKat">text2</td>
<td class="ELEM">s</td>
<td class="EL">sss</td>
</tr>
</table>
I have so many classes, because the elements are create dynamically and so the IDs are not a variant.
And the function, in which the error shows up (error as a comment):
var sort=function(cmpFunc) {
var table=document.getElementById("output");
var kat=document.getElementsByClassName("ElemKat");
var elem_kat=new Array();
for(var i=0; i<kat.length; i++) {
elem_kat.push(kat[i]);
}
var elem_kat_old=new Array();
elem_kat_old=elem_kat;
elem_kat.sort(cmpFunc);
for(var j=0; j<elem_kat.length; j++) {
//table=elem_kat_old[j].parentNode; ->No error,but no change in the output
table.replaceChild(elem_kat[j],elem_kat_old[j]); //Node was not found
}
}
Instead of elem_kat_old = elem_kat;, try this:
elem_kat_old = elem_kat.slice(0)
Assigning an array to another array makes it so that whatever you do to one array, happens to the other array. This isn't quite assignment by reference, but I am not 100% sure of the proper Javascript term for it.
I'm doing a directive which is gonna work similar to an excel.
As Javascript does not support matrix as native, I had to do the workaround that usually people does. Array inside array.
Everything is working so far, but I've realized that when I applies a matrix of 100x100 I do have a big problema with performance. With a small matrix it's ok, but with big ones it's pissing me off.
Another big deal for me is that while this directive does not finish its loading the rest of the APP does not load if the problematic directive does not finish.
How can I improve this my current approach to have a better performance? And
How can I make the APP to load each part of itself in an asynchronous way?
From my service I have this:
this.getDataGrid = function(column, row){
var matrix = new Array(column);
for (var i = 0; i < column; i++) {
matrix[i] = new Array(row);
}
for (var i = 0; i < matrix.length; i++){
for (var j = 0; j < matrix[i].length; j++){
matrix[i][j] = {value: '', checked: false};
}
}
return matrix;
};
From my directive:
link: function postLink(scope, element, attrs) {
scope.dataGrid = info.getDataGrid(attrs.column, attrs.row);
}
From my view:
<table border="1">
<thead>
...
</thead>
<tbody>
<tr ng-repeat="items in dataGrid track by $index">
<td><strong>{{$index+1}}</strong></td>
<td ng-repeat="item in items track by $index"><input type="text" ng-model="item.value"/></td>
</tr>
</tbody>
</table>
UPDATE:
I gave a try to quick-ng-repeat as mentioned by apairet, but didn't work so far. Looks like the quick-ng-repeat doesn't work with nested array.
<tr quick-ng-repeat="item in dataGrid" quick-repeat-list="items">
<td><strong>{{$index+1}}</strong></td>
<td quick-ng-repeat="subItem in items" quick-repeat-list="subItems"><input type="text" ng-model="subItems.value"/></td>
</tr>
UPDATE:
After some research I started to go throughout to the lazy load approach. I've found some projects with RequireJS and Sript.js and I thought this was the solution for my problem, until I realize I had a diff situation.
The lazy load approach works by a route, it loads the related files of the route in a lazy way.
However, what about when you have inside the view of your route a directive which makes everything slow? That's my case.
So, I had to do some workaround as it follows below:
DIRECTIVE
link: function postLink(scope, element, attrs) {
var maxIntervalColumnLoad = 5;
var maxIntervalRowLoad = 5;
scope.gridLoadColumn = function(index){
if (index <= maxIntervalColumnLoad) {
return true;
}else{
return false;
}
}
scope.gridLoadRow = function(index){
if (index <= maxIntervalRowLoad) {
return true;
}else{
return false;
}
}
$interval(function(){
maxIntervalColumnLoad = (maxIntervalColumnLoad + 5);
}, 1500, (attrs.column/maxIntervalColumnLoad) );
$interval(function(){
maxIntervalRowLoad = (maxIntervalRowLoad + 5);
}, 1500, (attrs.row/maxIntervalRowLoad) );
scope.dataGrid = info.generateDataGrid(attrs.column, attrs.row);
}
VIEW
<thead>
...
</thead>
<tbody>
<tr ng-if="gridLoadRow($index)" ng-repeat="items in dataGrid track by $index">
<td><strong>{{$index+1}}</strong></td>
<td ng-if="gridLoadColumn($index)" ng-repeat="item in items track by $index"><input type="text" ng-model="item.value"/></td>
</tr>
</tbody>
It solved the problem with my first load page.
But I still have performance problems when I'm typing any value in the inputs.
<input type="text" ng-model="item.value" />
I'm guessing the relation through the ng-model seems to be the problem, but I need it to keep the data sync.
Any idea how could it be achieve? Or How to improve the performance?
Being quite new to angular, I am searching the best way to achieve a quite simple task.
My aim is to update in a database, through angular $resource service, the order (I have a position attribute) of a Project model.
I have the following template structure :
<table class="table table-hover">
<thead>
<tr>
<th>#</th>
<th>Title</th>
<th>Date</th>
<th>Link</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody id="sortable" my-drag-list>
<tr ng-repeat="project in projects" ng-class="{warning: !project.publish}">
<td></td>
<td>{{project.title}}</td>
<td>{{project.date|date: 'MMMM yyyy'}}</td>
<td><a ng-href="{{project.link}}" target="blank">{{project.link}}</a></td>
<td><a ng-href="#/projects/{{project.id}}/"><span class="glyphicon glyphicon-edit"></span></a></td>
<td><a ng-click="deleteProject(project)"><span class="glyphicon glyphicon-trash"></span></a></td>
</tr>
</tbody>
</table>
<button type="button" class="btn btn-success" ng-show="data.saveSort" my-update-sort-button>Save new sort</button>
tr in my tbody element are movable. When I click on the button element (which is out of the scope created by the ng-repeat directive), I want to update my database whith new position values, which are determined by the dom position of each tr (upper tr will have a smaller position value).
At first glance, I intends to do a my-update-sort-button directive with the following :
var link = function(scope, el){
el.on('click', function(){
var els = el.parent().find('tr');
for(var i = 0, len = els.length; i<len; i++){
Projects.update({id: els.eq(i).data('projectId')}, {position: i});
}
});
};
But I am not sure about the "quality" of such solution. I do not like the fact of adding data-project-id attribute on my tr element.
Thanks for any ideas or solutions for this case !
You need to keep info about "project" position in it's model. When "project" is moved you save this info to its model. Do it in "my-drag-list".
When click to update button you just send info from model without scanning the DOM:
var link = function(scope, el){
el.on('click', function(){
var model;
for(var i = 0, len = scope.model.projects.length; i<len; i++){
model = scope.model.projects[i];
Projects.update({id: model .id}, {position: model.position});
}
});
};
Even better don't send a lot of requests to server. Send one request with all info together.