Angularjs toggle between input and span values in a table row - javascript

I have following code in my html where I want to toggle input and span fields. In a table row.
<table>
<tbody ng-repeat="(i, cont) in char.items">
<tr>
<td>
<div>
<a ng-click="choose()">
<input type="text" ng-model="item.desc" ng-show="sho==1" />
<span ng-show="sho==0">{{item.type}}</span></a>
</div>
</td>
</tr>
</tbody>
</table>
<div ng-click="addRows(char)" style="WIDTH: 974px">Add Row</div>
In my controller I have
app.controller("testCtrl", function($scope) {
$scope.sho=0;
$scope.addRows = function(char) {
if (typeof char.items == 'undefined') {
char.items = [];
}
char.items.push({ des: '', type: '', price: '', charge__id: ''});
};
$scope.choose= function() {
//some values are retrieved than I want to toggle so it shows the
//want to set sho=1 so input is hidden instead the span vaue is shown
$scope.sho=1;
};
});
Problem is when I set $scope.sho=1; it shows span value in all the row of the table.
While I add a new row I just want to show the input box leaving the other rows already inserted with span values.
Pleae let me know how can i set ng-show for each row in table.
Thanks

Since ng-repeat creates a child scope for each item you can leverage that within a directive. The parent scope of the directive will be the child scope created by ng-repeat and therefore isolated from other repeaters
Move your choose and sho out of main controller and put them into directive scope.
<div editable>
<a ng-click="choose()"></a>
<input type="text" ng-model="item.desc" ng-show="!sho" />
<span ng-show="sho">{{item.type}}</span>
</div>
app.directive('editable', function () {
return function (scope, elem, attrs) {
scope.sho = true;
scope.choose = function () {
scope.sho = !scope.sho;
}
}
});
This is the simplest version possible without going to isolated scope within the directive and without considering factors like more than one of these editables in a row.
For more insulated feature rich version would consider using a more robust directive like x-editable

I have trouble understanding what your code is actually used for. But my guess would be for you to pass the current item into the choose function and set a flag on the item itself. If you modify your ng-show and ng-hide attributes to react to this flag on each item, I guess you would reach your goal.
<a ng-click="choose(item)">
<input type="text" ng-model="item.desc" ng-show="item.sho==1" />
<span ng-show="item.sho==0">{{item.type}}</span></a>
</div>
And in your choose function you would do something like this:
$scope.choose= function(item) {
item.sho=1;
};
This is only a wild guess though, since it isn't quite clear to me what you are trying to accomplish.

Two things that come to mind immediately are:
1 - Pass in the item with the function and have the function accept an argument.
<a ng-click="choose(sho)">
and then in your controller
$scope.choose= function(sho) {
sho = 1;
};
2 - Just make ng-click set the value to one..
<a ng-click="sho = 1">

Related

AngularJS copy existing table row

I have a table with an edit and delete button. But now I also want to make a clone button.
The clone button should work as follows: It clones almost all the data (data such as the id he cannot take) from the row that the user has clicked. Then it goes to the edit page and there it fills the data in the input/select values.
But I have no idea how I get this done.
I have now a function which output all the data: var cloneJob = angular.extend(job);
Then it goes to the edit page location.href = '#/jobs/add';
But the problem is that he doesn't fill the input/select values.
Does AngularJS has a function for this? And am I on the right track or do I need to do something else?
UPDATE
Here is a litle bit more code:
This is my the code of my table:
<tr ng-repeat="job in (filtered.rows = (jobs | orderBy: orderByDate:true | filter:filterByActive | filter:filter.query)) | skip:pagination.pageSkip() |limitTo:pagination.perPage" ng-class="{ inactive : !job.active }" style="cursor: pointer;">
<td>
<span ng-bind="job.title"></span>
</td>
<td>
<span ng-bind="job.client.name"></span>
</td>
<td>
<span ng-bind="job.referenceNumber"><span>
</td>
<td>
<span ng-bind="job.creationDate"><span>
</td>
<td>
<a ng-href="#/jobs/edit/{{job.id}}/tab/candidates" ng-bind="job.candidates.length"></a>
</td>
<td>
<span class="status" ng-class="job.status.value"></span>
</td>
<td>
<a ng-if="job.active" ng-href="#/jobs/edit/{{job.id}}" class="icon go">
<span class="tooltip" translate="job_name_details"></span>
</a>
<a ng-if="job.active" class="icon close" ng-click="showClosePopup(job)">
<span class="tooltip" translate="job_close"></span>
</a>
<a ng-click="cloneJob(job)" ><span>Clone!</span></a>
<!-- <button data-ng-click="cloneItem(food)" class="btn inline">Add</button> -->
</td>
</tr>
Function cloneJob is:
$scope.cloneJob = function (job){
var cloneJob = angular.extend(job);
location.href = '#/jobs/add';
}
This outputs a lot of json (all the correct data) and it goes to the add page.
Try something like
<tr ng-repeat="whatever in whatevers"><button ng-click="duplicateItem(whatever)">duplicate</button></tr>
And on the controller:
$scope.duplicateItem = function(item){
$scope.duplicatedItem = angular.copy(item); //this will do a copy, not just assign a reference.
//if you need clean the duplicate item
delete $scope.somePropertyYouWannaClean;
}
It would better if you provided a working example fiddle or at least more code, so we can give you more accurate answers.
Edit:
A cleaner way would be to make the clone function load the info into a service (or factory, a singleton). Then after loading the route you use that service to get the content back and play with it.
Like:
angular.module('some.namespace.factory', [])
.factory('CloneJobFactory', function () {
return {
job: null,
loadJob: function (job) {
var auxJob = angular.copy(job);//if you just need a shallow copy use angular.extend
this.job = this.cleanJob(auxJob);
},
getClonedJob: function(){
return this.job;
},
cleanJob: function(job) {
//code that cleans a job object that has been cloned
delete job.propertyYouDoNotWantToKeep;
return job;//return the cleaned job
}
};
});
Then the clone function that would be in the controller (that now has to inject the factory we just made) just has to wrap the loadJob method:
$scope.cloneJob = function (job) {
CloneJobFactory.loadJob(job);
}
The same for the function that would use the cloned data:
$scope.someFunction = function (whateverParams) {
var clonedJob = CloneJobFactory.getClonedJob();
//whatever you want
}
This can still be improved.
NOTE: Angular singletons are made to, among other things, share info between controllers, services and so on.
Make a new 'clone' route, that uses the same controller and view as your 'add' route, but passes in the id of the job that should be cloned:
.when('/jobs/add', { templateUrl: 'jobs/add', controller: 'AddController' })
.when('/jobs/clone/:id', { templateUrl: 'jobs/add', controller: 'AddController' })
Then, in the AddController, check if an id has been passed using $routeParams. If there is an id, fetch the job with the id, and initialize the model by cloning the job. If there's no id, initialize the model with an 'empty' job.
myModule.controller('AddController', function($scope, $routeParams){
if($routeParams.id) {
//TODO get existing job using $routeParams.id
$scope.newJob = angular.copy(job);
} else {
$scope.newJob = {};
}
});

why is my list not rendering?

by default it shows 10 elements, but when i change the input it does not update, below is the code and fiddle.
JS Code:
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.listItems = 10;
$scope.newTotal = function(){
$scope.$apply(function(){$scope.lisItemsTotal})
}
$scope.lisItemsTotal = function(num) {
return new Array($scope.listItems);
}
});
http://jsfiddle.net/0dwmqn8y/1/
The reason is that after changing the input $scope.listItems is a string, as the input type is text. Change it to number and all will work. Working plunker: http://jsfiddle.net/yv0z9q8L/
<input type="number" name="red" ng-model="listItems" onchange="angular.element(this).scope().newTotal()">
Also note, that you don't need onchange attribute at all here, however I am unsure whether this is a good thing. Using a function in for ng-repeat is quite dangerous unless you know what you are doing, as this function will be called in each digest cycle. If you are planning to loop over large set of data, it will pretty much kill your performance.
Personally, I would rather go with:
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<input type="number" name="red" ng-model="itemCount">
<ul>
<li ng-repeat="i in items track by $index"><span>{{$index+1}}</span></li>
</ul>
</div>
</div>
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.$watch('itemCount', function(val) {
$scope.items = Array.new(val)
});
$scope.itemCount = 10;
});
plunker: http://jsfiddle.net/m1zq3zqv/1/
The data, that you get from the input field is a string, so you can either change the input type to number or apply a bit of parsing in your code by changing the following line
return new Array($scope.listItems);
to this:
return new Array(parseInt($scope.listItems));

AngularJS nested ng-repeat click & show/hide

I've done a ton of reading and research on this topic the past few days and have found some good answers, but for some of the answers I question performance and necessity.
My question pertains to nested ng-repeat scopes. I'm wondering what the best way to achieve an "add item" scenario for adding an item to the nested foreach.
My Code
My HTML is simply 2 ng-repeats and my goal is to be able to add an item to the second (nested) ng-repeat
<div ng-app="myApp">
<div class="nav" ng-controller="FoodsController as vm">
<div class="level1" ng-repeat="foods in vm.foodGroups">{{foods.Name}}
<button type="button" ng-click="vm.addNewFood()">add new food</button>
<div ng-show="vm.newFoodBeingAdded">
<input type="text">
</div>
<div class="level2" ng-repeat="food in foods.FoodsInGroup">{{food.Name}}</div>
</div>
</div>
</div>
My Angular controller looks like this:
app.controller('FoodsController', function () {
var vm = this;
vm.foodGroups = [{
"Name": "Grains",
"FoodsInGroup": [{
"Name": "Wheat"
}, {
"Name": "Oats"
}]
}, {
"Name": "Fruits",
"FoodsInGroup": [{
"Name": "Apple"
}, {
"Name": "Orange"
}]
}];
vm.newFoodBeingAdded = false;
vm.addNewFood = function () {
vm.newFoodBeingAdded = true;
};
});
What should happen
The general work flow would be a user clicks an Add New button and it shows a text box with a "save" button. The text box & button would be within the parent foreach. Once a user saves the item it would then be added to the nested foreach (that logic isn't shown).
The issue
The issue is that when I click "Add New Food" (which should just show 1 of the text boxes & save buttons), all of the text boxes show. How do I ensure I am "scoping" this correctly and that only the text box/button within that parent are shown?
Possible solution
One answer I found was to create a child controller for each nested item. For example I'd have a FoodGroupsController which would manage all the logic for the nested foreach (because there will be a lot more going on than just adding a new item in a real app, so it could be justified).
jsFiddle
Here's a jsFiddle with the code that currently does not function correctly.
There is the forked Fiddle
I made just few changes. The fact is that you were binding the ng-show with a single var in your controller. It was a show me all or show me nothing possibility.
So the fix it, you have to bind this, in your food item, not in the controller himself.
Html :
<button type="button" ng-click="vm.addNewFood(foods)">add new food</button>
<div ng-show="foods.newFoodBeingAdded" class="add-new-food">
<input type="text" placeholer="add a new food">
<button type="button">save new food</button>
</div>
Controller :
vm.addNewFood = function (foods) {
foods.newFoodBeingAdded = true;
};
With this code, you pass the food in param of your function, so you can change the boolean of your food only. And then, your ng-show is just binding on this boolean.

Update unrelated field when clicking Angular checkbox

I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];

Checking if any checkbox is checked Knockout - PhoneJS

So in my mobile web app (using PhoneJS), I am using a dxList to display some records. I have a checkbox next to each list 'item', so that I can mass delete or send the records. I need to know how to figure out if there is one or more checkboxes checked.
I know I can do this with normal Knockout, but I don't the PhoneJS framework actually creates a 'real' HTML checkbox, but makes a clickable element that functions like a checkbox.
So if one or more checkboxes are checked, I need to show a send and delete button. I just need to know how to determine if there are any checked boxes.
I've looked everywhere online for this, but the solutions are for Knockout using REAL checkbox inputs...
Here's my code for the dxList:
<div data-bind="dxList:{dataSource: list_data, grouped:true }">
<div data-options="dxTemplate:{name:'group'}">
<b><span data-bind="text: $data.key"></span></b>
</div>
<div data-options="dxTemplate:{name:'item'}">
<span data-bind="text: $data.item_value"></span>
<div data-bind="dxCheckBox: { }" style="float:right"></div>
</div>
</div>
I've tried binding 'checked' to an observable array, but that affects all the checkboxes.
Can anyone help me with this? Thanks!
The most straightforward MVVM approach is to data-bind dxCheckBox.checked option to a boolean property of a list item view-model. Then you can iterate over the items and understand which are checked.
You mentioned that you
tried binding 'checked' to an observable array
It is not clear why you bind a scalar property to an array.
Actually it does not differ much from the pure HTML approach. You may treat PhoneJS widgets just as fat HTML tags.
So, I have pretty much the same question, but I think I can be more clear on my requirements.
I have a dxList that uses a SQLite table as a datasource. It is setup to allow the user to select from a list of templates to apply to another object. This new list of templates and the associated object ID will be saveed in a DIFFERENT table than the original data and as such, I need to be able to identify the items in the list that have been checked.
<div data-bind="dxList: { dataSource: templateList }">
<div data-bind="dxAction: ''" data-options="dxTemplate : { name: 'item' } ">
<table>
<tr>
<td>
<div data-bind="dxCheckBox: { }"></div>
</td>
<td>
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: TemplateName"></div>
</td>
</tr>
</table>
</div>
</div>
I found this post during my initial search. I can't use the data-bind: {checked: ?} value of each check box, as that would do as the original poster found, setting all or none. I thought about an array. I'm going to try to use the dxAction to add/remove checked list item IDs from an array but I'm not sure how well that will work. Then there's the final parse to get all checked items. I will update this post once I get it working.
Resolution:
ViewModel objects:
selectedTemplates: ko.observableArray(),
selectTemplate: function (args) {
//If it's there. Remove it.
if (args.model.selectedTemplates.indexOf(args.itemData.TemplateID) > -1) {
args.model.selectedTemplates.pop(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '';
args.itemElement[0].style.color = 'Black';
}
//else Add
else {
args.model.selectedTemplates.push(args.itemData.TemplateID);
args.itemElement[0].style.backgroundColor = '#017AFF';
args.itemElement[0].style.color = 'White';
}
},
And the View:
<div data-options="dxView : { name: 'SelectSurveys', title: 'SelectSurveys' } ">
<div data-bind="dxCommand: { title: 'Save', id: 'create', action: saveSelections, icon: 'save' }"></div>
<div data-options="dxContent : { targetPlaceholder: 'content' } ">
<div data-bind="dxList: { dataSource: templateList, itemClickAction: selectTemplate }">
<div data-options="dxTemplate : { name: 'item' } ">
<div style="font-weight: bold; padding-left: 10px;" data-bind="text: SurveyName"></div>
</div>
</div>
</div>
</div>
And looping the selected values for saving to local DB:
$.each(args.model.selectedTemplates(), function (index, value) {
db.transaction(function (tx) {
console.log("Inserting Data");
tx.executeSql(sql, [value],
function (t, result1) {
if (result1 != null) {
console.log("New Item added." + result1.insertId);
}
},
function (t, error) {
console.log(error);
});
});
In the objects, I've added some coloring so you can tell which ones are selected, it doesn't use the dxSwitch or Checkbox, but it works just as well and I think it's more visually appealing as well as informative to the user.

Categories