How to make a custom HTML id by evaluating an expression - javascript

I try to make a version of "tic tac toe" with AngularJS and be as minimalistic as possible. The only solution for my problem is to assign every button a unique ID (f+i).
HTML
<table>
<tr ng-repeat="f in [5,10,15]">
<!-- numbers chosen for unique combos-->
<td ng-repeat="i in [0,1,2]">
<button ng-click="toTrue()" >
<div >
{{getXO()}}
</div>
</button>
</td>
</tr>
</table>
JavaScript
$scope.XObool=false;
$scope.toTrue = function() {
if(!$scope.XObool){
$scope.XObool=true;
}
else if($scope.XObool) {
$scope.XObool=false;
}
};
$scope.getXO = function(){
if($scope.XObool){
return 'X';
}
else {
return 'O';
}
};

ng-repeat gives you several variables to work with, namely $index. In your case you'll want something like:
<button id="{{$index}}" ...>
More info on the ng-repeat docs.
Second Option
Use the f and i variables to create unique IDs.
<table ng-app>
<tr ng-repeat="f in [5,10,15]" data-id="{{$index}}">
<td ng-repeat="i in [0,1,2]">
<button id={{'id' + (i+f)}} ng-click="toTrue()">
{{'id'+(i+f)}}
</button>
</td>
</tr>
</table>
Here's a demo.

You don't need to assign each button a unique ID.
Instead, you can pass your f and i variables into your functions to track the board state:
<table>
<tr ng-repeat="f in [0,1,2]">
<!-- numbers chosen for unique combos-->
<td ng-repeat="i in [0,1,2]">
<button ng-click="setState(f, i)">
<div >
{{ getXO(f,i) }}
</div>
</button>
</td>
</tr>
</table>
Working fiddle here: http://jsfiddle.net/m76w3kf5/

try markup binding with {{}} or $index
<div id="{{someObject.id}}" class="some-class" ng-repeat="f in [ array ]">
..
</div>
or a slightly extended example with $index, listing thumbnails in array with clicks to reference position by index
<tr ng-repeat="array in thumbnails track by $index">
<td ng-repeat="object in array track by object.id"
ng-click="tableClickHandler($index, object)">
<img class="user-thumbnail" src="{{object.src}}">
</td>
</tr>

Related

How to bind an object to HTML table in Vue.js, where its properties are dynamically created?

I am using vue.js library for front-end development.
I came a cross a scenario where my JavaScript method returns a list, which has objects, object's number of properties can change each time after method execution.
example my list can contain these type of objects in 2 different executions.
var obj = {
Name : "John",
2020-Jan: 1,
2020-Jul: 2
}var obj = {
Name: "John",
2020-Jan: 1,
2020-Jul: 2,
2021-Jan: 3,
2021-Jul: 4
}
Since Property name is dynamically changes is there any way to bind to HTML ?
<div >
<table>
<thead>
<tr>
<th v-for ="row in Result.Headers">
{{row}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in Result.Data ">
<td>
{{item.2020-Jan}} // Because don't know the number of properties until run time
</td> // No of <td/>'s can change on no of properties.
<td> // exactly don't know how many <td>'s needed there.
{{item.2020-Jul}}
</td> <td>
{{item.2021-Jan}}
</td>
</tr>
</tbody>
</table>
</div>
Is there way to bind these type of object to fronted in vue.js ?
You need to loop over the item's keys again. This will show all the values in the object
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="(value, key, index) in item">
{{value}}
</td>
</tr>
</tbody>
If you want to filter some of them, for instance check that the keys are valid dates you need to add a v-if and use Date.parse to check for this.
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="(value, key, index) in item" v-if="Date.parse(key) !== NaN">
{{value}}
</td>
</tr>
</tbody>
if u wana show all attr-> u can use this:
<ul v-for="item in Result ">
<li v-for="(value,key,index) in item">{{value}}</li>
</ul>
if u wana show all days u can use v-if and compute to complete youself fillter
<div id="app">
<ul v-for="item in Result" >
<li v-for="(value,key,index) in item" v-if="canShow(key)"> index:{{index}}------ key: {{key}} ------ value:{{value}} </li>
</ul>
</div>
<script>
var vue=new Vue({
el:'#app',
data:{
Result:[{
name: 'SkyManss',
2020-Jan: 1,
2020-Jul: 2
},{
name: 'SkyManss2',
2020-Jan: 1,
2020-Jul: 2,
2021-Jan: 3,
2021-Jul: 4
}]
},
computed:{
canShow(){
return function(skey){
return skey.indexOf('-') > -1;
}
}
}
});
</script>
after some research and some of your suggestions I came up with an answer.
<div>
<table>
<thead>
<tr>
<th v-for ="row in Result.Headers">
{{row}}
</th>
</tr>
</thead>
<tbody>
<tr v-for="item in Result.Data ">
<td v-for="row in Result.Headers">
{{item[row]}}
</td>
</tr>
</tbody>
</table>
</div>
Javascript code
this.Result.Headers = Object.keys(result.data[0]);
this.Result.Data = result.data;
But this code only worked for the first time. second time data didn't get updated. So I updated JavaScript code to following code.
Vue.set(self.Result, 'Headers', []);
Vue.set(self.Result, 'Result', []);
this.Result.Headers = Object.keys(result.data[0]);
this.Result.Data = result.data;
Vue does not allow dynamically adding new root-level reactive properties to an already created instance. That I got to know from following post.
vue.js is not updating the DOM after updating the array
Thank You All !!!

nested ng-repeat that one depend on the other

I have an issue with angular, I want to use two nested ng-repeat in a data table where the first get the data, and the second get the name of field to be retrieved from the data (retrieved in the first ng-repeat)
here is what I tried to do with code :
<table md-table flex-order-gt-sm >
<thead md-head>
<tr md-row >
//1st : get the name of fields, not a problem
<th md-column ng-repeat="field in fields">{{item.name}}</th>
</tr>
</thead>
<tbody md-body>
<tr md-row ng-repeat="item in items">
<td md-cell ng-repeat="field in fields" >
//here is the problem I want to get the item that it's field name is field
{{item.{{field}} }}</td>
</tr>
</tbody>
</table>
for example if fields contain :{'a','b','c'}
and items contains {'a':'1','b':'2','c':'3'};
I want for example for the 1st iteration of {{item.{{field}} }} to return 1
Use toString() to retrieve the scope data in an scope object.
<tr md-row ng-repeat="item in items">
<td md-cell ng-repeat="field in fields" >
{{item[field.toString()]}}
</td>
</tr>
Here is the plunker
As an alternative you could also use a filter:
<td md-cell ng-repeat="field in fields | filter: { name: 'field' }">
</td>
You need to get fields collection according to item of items collection after
getFieldsByItem(item)
Then you can get field from fields collection
{{field}}

ng repeat in table using Angularjs

The Array I use is [{"cell":["jobcode","resume_number","score"]},{"cell":["jc100","rc1",80]},{"cell":["jc100","rc123",70]}]
And I came up with javascript code as
var cell=response;
for (var i in cell) {
for(var j in cell[i])
{
console.log(cell[i][j]);
profiles.push(cell[i][j]);
$scope.profiles=profiles;
for(k in cell[i][j])
{
resumes.push(cell[i][j]);
console.log("resume length"+resumes.length);
$scope.columns=resumes;
console.log(JSON.stringify($scope.columns));
}
}
}
And html is
<tr ng-repeat="profile in profiles track by $index" >
<td ng-repeat="col in columns track by $index">
<label >{{col.cell}}</label>
</td>
</tr>
And ended up enter image description here
I have no idea to proceed further. I need to organize those data as a table. Please help.
Your data contains an array of objects which contains another array. Therefore, you need to extract each object from outer array and then go down to inner one.
If you need this data only to organise them in a table, then you can simply use the following code:
Your Controller code:
$scope.cell = response;
Your HTML :
<tr ng-repeat="profile in cell track by $index" >
<td ng-repeat="col in profile.cell track by $index">
<label >{{col}}</label>
</td>
</tr>
In case you need to store each array object, then you can use forEach loop:
Your controller code:
var cell=[{"cell":["jobcode","resume_number","score"]}, {"cell":["jc100","rc1",80]}, {"cell":["jc100","rc123",70]}];
angular.forEach(cell, function(data){
$scope.profiles.push(data);
});
Your HTML :
<table>
<tr ng-repeat="profile in profiles track by $index" >
<td ng-repeat="col in profile.cell track by $index">
<label >{{col}}</label>
</td>
</tr>
</table>
Try the following code. Take header as separate part from json and display header first then start ng-repeat from index first
<table border="1">
<tr>
<td>
{{columns[0].cell[0]}}
</td>
<td>
{{columns[0].cell[1]}}
</td>
<td>
{{columns[0].cell[2]}}
</td>
</tr>
<tr ng-repeat="col in columns" ng-if="$index>0">
<td>
{{columns[$index].cell[0]}}
</td>
<td>
{{columns[$index].cell[1]}}
</td>
<td>
{{columns[$index].cell[2]}}
</td>
</tr>
</table>

ng-repeat run function for every row?

I have a ng-repeat and I need to run a function to get some additional data/few calculations for every row.
Something like this
<div id="complaintstable">
<table>
<tr ui-sref="complaints.details({ id: item.Id })" ng-repeat="item in list" class="item" ng-class-odd="'odd'" ng-class-even="'even'" ng-init="rowInit(item)">
<td>
{{item.ConsumerName}}
</td>
<td>
{{dayspan}}
</td>
<td>
{{item.ConsumerCity}}
</td>
<td>
{{item.Agent.First_Name}} {{item.Agent.Last_Name}}
</td>
<td>
{{compname}}
</td>
<td>
{{item.DateOpenedDisplay}}
</td>
</tr>
</table>
</div>
And then in the directives controller
$scope.rowInit = function (row) {
$scope.dayspan = someAJAXCall();
$scope.compname = someCalculation();
console.log("here");
}
Obviously the problem with this is that rowInit gets evaluated in the controller scope and no in the ng-repeat scope, so dayspan and compname get overwritten. How would I go about evaluating rowInit in the ng-repeat scope?
Iterate your list and do the calc for each row, add the data to the row, then display item.compname and item.dayspan in your markup.
$scope.list.forEach(
function(item) {
item.dayspan = someAJAXCall();
item.compname = someCalc();
}
);
Note, a boat load of AJAX calls concurrently is problematic - but that's not a problem asked about in your question.

jsViews - re-evaluate 'if' tag on root property change

I have created a table/grid using jsViews. Each row has an edit button which when clicked selects the row and shows input controls instead of text values.
If I show/hide the inputs using data-link="visible{:#parent.parent.data.selectedIndex!==#index}" then it works fine.
However, I was trying a different approach using {^{if #parent.parent.data.selectedIndex===#index}}...{{else}}...{{/if}} to show/hide the inputs and this doesn't work when selectedIndex changes on my data object.
I also tried with {^{if ~root.selectedIndex===#index}} but that didn't work either. Is it possible to do this with {{if}}? The reason I am trying this over the first method that worked was to avoid rendering lots of select boxes which will just be hidden anyway.
My data object looks like this:
app = {
data: [...],
selectedIndex: null,
select: function select(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
I link the template like this:
$.templates("#myTemplate").link("#divHolder", app)
.on("click", ".data .editButton", function() {
app.select($.view(this).index);
})
.on("click", ".data .saveButton", function() {
// save details
})
.on("click", ".transmittals .cancelButton", function() {
// reset values
app.select(null);
});
My template is like this:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
<tr>
<th></th>
<th>A</th>
<th>B</th>
<th>C</th>
</tr>
</thead>
<tbody>
{^{for data}}
<tr class="item">
<td>
{{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem">
<button class="cancelButton">Cancel</button></span>
{{else}}
<span class="viewItem">
<button class="editButton">Edit</button></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="B" /></span>
{{else}}
<span class="viewItem" data-link="B"></span>
{{/if}}
</td>
<td>
{^{if #parent.parent.data.selectedIndex===#index}}
<span class="editItem"><input type="text" data-link="C" /></span>
{{else}}
<span class="viewItem" data-link="C"></span>
{{/if}}
</td>
</tr>
{{/for}}
</tbody>
</table>
</script>
When you add an {{if}} block, it is a nested view, so the button click is not getting you the item view with the index. You need to use $.view(this).parent.index - or, simpler, $.view(this).getIndex() - which automatically steps up through nested views (if any) to the item view and gets its index.
app.select($.view(this).getIndex());
(See discussion here: https://github.com/BorisMoore/jsrender/issues/173#issuecomment-11058106)
BTW here is a modified form of your sample, just to give you some ideas. It uses <button data-link="{on ~root.select #getIndex()}">Edit</button> to hook up the click handler on the button and call the select method directly, passing it the index:
<script id="myTemplate" type="text/x-jsrender">
<table id="tblData" class="data">
<thead>
...
</thead>
<tbody>
{^{for data}}
<tr class="item">
{^{if ~root.selectedIndex===#index}}
<td><button class="cancelButton" data-link="{on ~root.select null}">Cancel</button></td>
<td><input data-link="A" /></td>
<td><input data-link="B" /></td>
{{else}}
<td><button class="editButton" data-link="{on ~root.select #getIndex()}">Edit</button></td>
<td data-link="A"></td>
<td data-link="B"></td>
{{/if}}
</tr>
{{/for}}
</tbody>
</table>
</script>
<div id="divHolder"></div>
<script>
var app = {
data: [{A:"aa", B: "bb"},{A:"aa2", B: "bb2"}],
selectedIndex: null,
select: function(index) {
if (this.selectedIndex !== index) {
$.observable(this).setProperty("selectedIndex", index);
}
}
};
$.templates("#myTemplate").link("#divHolder", app);
</script>

Categories