Say I have an object stored in $scope like so:
$scope.todo = [
{
"title" : "Groceries",
"todoItems" : [
{
"title" : "Milk",
"status" : "Not Done"
},
{
"title" : "Eggs",
"status" : "Not Done"
},
{
"title" : "Bread",
"status" : "Done"
}
]
},
{
"title" : "Medical",
"todoItems" : [
{
"title" : "Make eye doctor appointment",
"status" : "Not Done"
},
{
"title" : "Go to pharmacy",
"status" : "Not Done"
},
{
"title" : "Take vitamins",
"status" : "Done"
}
]
}
];
I am creating a feature that allows inline editing of each todo item, like so:
I achieve this by toggling a property on the todo list item called editMode. See lines 11-14 in the following code block:
<div ng-app="myApp">
<div ng-controller="dashBoard">
<div class="panel panel-default list-[(listID)]" ng-repeat="(listID, todoList) in todo" ng-cloak>
<div class="panel-heading">[( todoList.title )]</div>
<ul class="list-group">
<li ng-repeat="(itemID, todoItem) in todoList.todoItems" data-as-sortable="board.dragControlListeners" data-ng-model="items" class="status-[(todoItem.status)] todo-item todo-item-[(itemID)]" data-as-sortable-item>
<div class="input-group">
<span data-as-sortable-item-handle class="input-group-addon">
<input ng-click="toggleStatus(listID, itemID, todoItem.status)" type="checkbox" ng-checked="todoItem.status == 1">
</span>
<span ng-if="!todoItem.editMode" class="todo-item-label-wrapper">
<div ng-click="toggleEditMode(listID, itemID, 1)" class="todo-item-label">[(todoItem.value)]</div>
</span>
<span ng-if="todoItem.editMode" class="todo-input-wrapper">
<input show-focus="todoItem.editMode" ng-keyup="$event.keyCode == 13 && toggleEditMode(listID, itemID, 0)" type="text" ng-model="todoItem.value" class="form-control">
</span>
</div>
</li>
</ul>
</div>
</div>
</div>
When any given todo item is clicked, it goes into edit mode. The todo item stays in edit mode until the user hits enter. I'd like to make it impossible to have multiple todo items in edit mode at the same time. If you click on todo item "foo" and then click on todo item "bar", todo item "foo" should switch back to read-only mode.
I am currently achieving this by individually switching every todo item with angular.forEach(), e.g.:
$scope.toggleEditMode = function(listID, itemID, editMode) {
$scope.todo[listID].todoItems[itemID].editMode = editMode;
//Turn off edit mode on every todo item other than the one that was just clicked
angular.forEach($scope.todo[listID].todoItems, function(todoItem, foreignItemID) {
if (foreignItemID !== itemID) {
$scope.todo[listID].todoItems[foreignItemID].editMode = 0;
}
});
}
But I wonder if angular has some utility for this usecase that I should be using.
What I do in such a case is not having an editMode property on each item, but instead using a scope variable like $scope.currentEditItemId. Then you do something like this:
$scope.toggleEditMode = function (listID, itemID, enableEdit) {
if (enableEdit === 1) {
$scope.currentEditItemId = itemId;
// ... whatever you need to do here
}
}
And the HTML would look like this:
<span ng-if="itemId != currentEditItemId" class="todo-item-label-wrapper">
<div ng-click="toggleEditMode(listID, itemID, 1)" class="todo-item-label">[(todoItem.value)]</div>
</span>
<span ng-if="itemId == currentEditItemId" class="todo-input-wrapper">
<input show-focus="todoItem.id == currentEditItemId" ng-keyup="$event.keyCode == 13 && toggleEditMode(listID, itemID, 0)" type="text" ng-model="todoItem.value" class="form-control">
</span>
Revisiting this a bit, I realize one way you could update everything in the scope simultaneously is to instantiate each item in $scope through a constructor, and then update the constructor prototype. This doesn't really solve for the original use case I posed above (which is perhaps better stated as "update all items in scope except one") but I think it still has some useful applications.
So, if you want to have an item which, when clicked, updates lots of other items, you could do something like this:
HTML:
<div ng-app="myApp" ng-controller="toDo">
<div ng-click="toggleEdit(index)" ng-class="{{item.editable}}" ng-repeat="(index, item) in items"> {{item.title}} </div>
</div>
JS:
var app = angular.module('myApp', []);
function newItem(title) {
this.title = title;
}
newItem.prototype.editable = 'foo';
function toggleAll() {
newItem.prototype.editable = 'bar';
}
app.controller('toDo', function($scope) {
$scope.items = []
for (var i = 0; i <= 10; i++) {
var item = new newItem("item" + i);
$scope.items.push(item);
}
$scope.toggleEdit = function(index) {
toggleAll();
}
});
Result:
Here we see the class foo toggle to the class bar on all items when any given item is clicked:
Related
What I am trying to do:
I am trying to have collapsible accordion style items on a page which will expand and collapse on a click event. They will expand when a certain class is added collapsible-panel--expanded.
How I am trying to achieve it:
On each of the items I have set a click event like so:
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
and in the function toggleClass() I have the following:
expanded = false;
toggleClass() {
this.expanded = !this.expanded;
console.log(this.expanded)
}
The issue im facing:
When I have multiple of this on the same page and I click one, they all seem to expand.
I cannot seen to get one to expand.
Edit:
The amount of collapsible links will be dynamic and will change as they are generated and pulled from the database. It could be one link today but 30 tomorrow etc... so having set variable names like expanded 1 or expanded 2 will not be viable
Edit 2:
Ok, so the full code for the click handler is like so:
toggleClass(event) {
event.stopPropagation();
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
console.log("contains class, remove it")
} else {
event.target.classList.add(className);
console.log("Does not contain class, add it")
}
}
and the code in the HTML is like so:
<div (click)="toggleClass($event)" class="collapsible-panel" *ngFor="let category of categories" >
<h3 class="collapsible-panel__title">{{ category }}</h3>
<ul class="button-list button-list--small collapsible-panel__content">
<div *ngFor="let resource of resources | resInCat : category">
<span class="underline display-block margin-bottom">{{ resource.fields.title }}</span><span class="secondary" *ngIf="resource.fields.description display-block">{{ resource.fields.description }}</span>
</div>
</ul>
</div>
you could apply your class through javascript
<div (click)="handleClick($event)">
some content
</div>
then your handler
handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
}
In plain html and js it could be done like this
function handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
console.log(event.target.classList.value);
}
<div onclick="handleClick(event)">
some content
</div>
Try to pass unique Id. (little modification)Ex: -
in component.ts file:
selectedFeature: any;
categories:any[] = [
{
id: "collapseOne",
heading_id: "headingOne",
},
{
id: "collapseTwo",
heading_id: "headingTwo",
},
{
id: "collapseThree",
heading_id: "headingThree",
}
];
toggleClass(category) {
this.selectedFeature = category;
};
ngOnInit() {
this.selectedFeature = categories[0]
}
in html:-
<div class="collapsible-panel" *ngFor="let category of categories">
<!-- here you can check the condition and use it:-
ex:
<h4 class="heading" [ngClass]="{'active': selectedFeature.id==category.id}" (click)="toggleClass(category)">
<p class="your choice" *ngIf="selectedFeature.id==category.id" innerHtml={{category.heading}}></p>
enter code here
-->
.....
</div>
Try maintaining an array of expanded items.
expanded = []; // take array of boolean
toggleClass(id: number) {
this.expanded[i] = !this.expanded[i];
console.log(this.expanded[i]);
}
Your solution will be the usage of template local variables:
see this: https://stackoverflow.com/a/38582320/3634274
You are using the same property expanded to toggle for all the divs, so when you set to true for one div, it sets it true for all the divs.
Try setting different properties like this:
<div (click)="toggleClass("1")" [class.collapsible-panel--expanded]="expanded1" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass("2")" [class.collapsible-panel--expanded]="expanded2" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
TS:
expanded1 = false;
expanded2 = false;
toggleClass(number:any) {
this["expanded" + number] = !this["expanded" + number];
console.log(this["expanded" + number])
}
I am developing one prototype application in ionic framework. I am newbie for angular js, HTML, CSS , Java Script and all this stuff.
I have one json file which I am using as an input. I am able to parse this Json file and able to get json object from this. This json object contains array of items. You can refer below json content for this. Here items are application A,B.....
Updated Input Json :
{
"data": [
{
"applicationname": "A",
"permissions": [
{
"text": "at"
},
{
"text": "at1"
}
]
},
{
"applicationname": "B",
"permissions": [
{
"text": "bt"
},
{
"text": "bt1"
}
]
}
]
}
When the application loads for the first time, application should load only the first item from above json array which means only application "A" (first item) data.
Once user clicks on any button (install/cancel) in Footer then it should changed its data and display application "B"'s contents. This process should continue till the end of json array.
My current code is not loading even the first item data in. Am I doing something wrong in HTML?
Updated Code :
HTML file :
<ion-header-bar class="bar-calm">
<h1 class="title">Application Permissions</h1>
</ion-header-bar>
<ion-nav-view name="home" ng-repeat="app in applicationdata">
<div class="bar bar-subheader bar-positive">
<h3 class="title"> {{app.applicationname }}</h3>
</div>
<ion-content class="has-subheader">
<div class="list" ng-controller="CheckboxController">
<ion-checkbox ng-repeat="item in app.permissions" ng-model="item.checked" ng-checked="selection.indexOf(item) > -1" ng-click="toggleSelection(item)">
{{ item.text }}
<h3 class="item-text-wrap"> details come soon </h3>
</ion-checkbox>
<div class="item">
<pre ng-bind="selection | json"></pre>
</div>
<div class="item">
<pre ng-bind="selection1 | json"></pre>
</div>
</div>
</ion-content>
<ion-footer-bar align-title="left" class="bar-light" ng-controller="FooterController">
<div class="buttons">
<button class="button button-balanced" ng-click="infunc()"> Install </button>
</div>
<h1 class="title"> </h1>
<div class="buttons" ng-click="doSomething()">
<button class="button button-balanced"> Cancel </button>
</div>
</ion-footer-bar>
</ion-nav-view>
app.js file :
pmApp.controller('CheckboxController', function ($scope, $http, DataService) {
// define the function that does the ajax call
getmydata = function () {
return $http.get("js/sample.json")
.success(function (data) {
$scope.applicationdata = data;
});
}
// do the ajax call
getmydata().success(function (data) {
// stuff is now in our scope, I can alert it
$scope.data = $scope.applicationdata.data;
$scope.devList = $scope.data[0].permissions;
console.log("data : " + JSON.stringify($scope.data));
console.log("first application data : " + JSON.stringify($scope.devList));
});
$scope.selection = [];
$scope.selection1 = [];
// toggle selection for a given employee by name
$scope.toggleSelection = function toggleSelection(item) {
var idx = $scope.selection.indexOf(item);
var jsonO = angular.copy(item);
jsonO.timestamp = Date.now();
DataService.addTrackedData(jsonO);
$scope.selection1 = DataService.getTrackedData();
// is currently selected
if (idx > -1) {
$scope.selection.splice(idx, 1);
}
// is newly selected
else {
DataService.addSelectedData(item);
$scope.selection = DataService.getSelectedData();
/* $scope.selection.push(item);*/
}
};
});
Problems :
1 : Why is the data of first item not getting loaded? I have done changes in HTML as per my understanding.
2 : How Can I navigate through all items. I will try #John Carpenter's answer. Before that first problem should be resolved.
Please help me, thanks in advance.
OK, so I'm not 100% sure what you want but I'll take a stab at it. In the future, it would be helpful to post less code (probably not the entire project you are working on). It is a good idea to make a simpler example than the "real" one, where you can learn what you need to learn and then go apply it to the "real" code that you have.
Anyways, this example is a simple button that you click on to change what is displayed.
var app = angular.module('MyApplication',[]);
app.controller('MyController', ['$scope', function($scope){
$scope.indexToShow = 0;
$scope.items = [
'item 1',
'item 2',
'item 3'
];
$scope.change = function(){
$scope.indexToShow = ($scope.indexToShow + 1) % $scope.items.length;
};
}]);
.simple-button {
cursor: pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="MyApplication" ng-controller="MyController">
<div ng-repeat="item in items track by $index" ng-show="$index == indexToShow">
{{item}}
</div>
<div class="simple-button" ng-click="change()">click me!</div>
</div>
I have an object which looks like this:
$scope.hobbies = {
list: [
{
"PersonId": 23,
"PersonName": "John Smith",
"Hobbies": [
{
"HobbyTitle": "Paragliding",
"HobbyId": 23
},
{
"HobbyTitle": "Sharking",
"HobbyId": 99
}
]
}
]
};
I'm trying to develop a view which allows users to make a selection of each person's hobby.
I have a plunker here
My problem is that all selected hobbies are displayed under every person. This is because I'm just pushing all selected hobbies to a selectedHobbies Array.
$scope.addHobbyItem = function (item) {
var index = $scope.selectedHobbies.list.indexOf(item);
if (index === -1) {
$scope.selectedHobbies.list.push(item);
}
};
This of course doesn't work, as once a hobby is selected, it is shown under every person. How could I adjust the code to work with the way I'm ng-repeating over the selectedHobbies?
The HTML is below. I'm also using a directive to listen to click on the hobby container and trigger addHobbyItem()
<div data-ng-repeat="personHobby in hobbies.list">
<div>
<div style="border: 1px solid black; margin: 10px 0 10px">
<strong>{{ personHobby.PersonName }}</strong>
</div>
</div>
<div class="">
<div class="row">
<div class="col-xs-6">
<div data-ng-repeat="hobby in personHobby.Hobbies" data-ng-if="!hobby.selected">
<div data-hobby-item="" data-selected-list="false" data-ng-class="{ selected : hobby.selected }"></div>
</div>
</div>
<div class="col-xs-6">
<div data-ng-repeat="hobby in selectedHobbies.list">
<div data-hobby-item="" data-selected-list="true"></div>
</div>
</div>
</div>
</div>
</div>
your selectedHobbies should be a map in which the key is the person id and the value is a list of his selected hobbies. checkout this plunker
$scope.selectedHobbies = {
map: {}
};
// Add a hobby to our selected items
$scope.addHobbyItem = function(pid, item) {
if(!$scope.selectedHobbies.map[pid]) {
$scope.selectedHobbies.map[pid] = [];
}
var index = $scope.selectedHobbies.map[pid].indexOf(item);
if (index === -1) {
$scope.selectedHobbies.map[pid].push(item);
}
};
in the directive call addHobbyItem with the person id
scope.addHobbyItem(scope.personHobby.PersonId, scope.hobby);
and lastly in you html iterate on each person's selected hobbies
<div data-ng-repeat="hobby in selectedHobbies.map[personHobby.PersonId]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>
something like this:
http://plnkr.co/edit/7BtzfCQNTCb9yYkv1uPN?p=preview
I used the $parent.$index to create an array of arrays containing hobbies for each person.
$scope.addHobbyItem = function (item, index) {
var ine = $scope.selectedHobbies[index].indexOf(item);
if (ine === -1) {
$scope.selectedHobbies[index].push(item);
}
};
function hobbyClickEvent() {
if (!$(element).hasClass('selected')) {
scope.addHobbyItem(scope.hobby, scope.$parent.$index);
} else {
scope.removeHobbyItem(scope.hobby);
}
}
and in the HTML:
<div data-ng-repeat="hobby in selectedHobbies[$index]">
<div data-hobby-item="" class="add-remove-container--offence" data-selected-list="true"></div>
</div>
I have code that populates then dropdownlist and the javascript variable that gets the last item in the list. Now all I want to do is select that last item as the default .What am I missing ?
<div class="row">
<div>
<select ng-init="lastItem" ng-model="congressFilter" ng-options="cc.congressLongName for cc in ccList"></select>
</div>
<div class="grid-style" data-ng-grid="userGrid">
</div>
ccResource.query(function (data) {
$scope.ccList.length = 0;
angular.forEach(data, function (ccData) {
$scope.ccList.push(ccData);
})
//Set default value for dropdownlist?
$scope.lastItem = $scope.ccList[$scope.ccList.length - 1];
});
You simply need to asign a value to congressFilter in your controller.
$scope.congressFilter = 'someVal';
It depends a little on how your data looks however.
It might help to new developers. need to add default id for display default item in option.
The below code sample we add [ $scope.country.stateid = "4" ] in controller $scope to set the default.
var aap = angular.module("myApp", []);
aap.controller("MyContl", function($scope) {
$scope.country = {};
$scope.country.stateid = "4";
$scope.country.states = [{
id: "1",
name: "UP"
}, {
id: "4",
name: "Delhi"
}];
});
<body ng-app="myApp">
<div ng-controller="MyContl">
<div>
<select ng-model="country.stateid" ng-options="st.id as st.name for st in country.states">
</select>
ID : {{country.stateid}}
</div>
</div>
</body>
I have a very weird issue, I have two observers in my App but only one of them fires properly. I am not sure why this is happening.
Here's the controller in question:
App.TwodController = Ember.ArrayController.extend({
//filteredContent : null,
init : function() {
this.set('filteredContent', []);
},
//sortProperties : ['firstname'],
//sortAscending : true,
selectedExperience : null,
filterExperience : function() {
var exp = this.get('selectedExperience.exp');
var filtered = this.get('arrangedContent').filterProperty('experience', exp);
this.set("filteredContent", filtered);
}.observes('selectedExperience'),
experience : [{
exp : "1"
}, {
exp : "2"
}, {
exp : "3"
}, {
exp : "4"
}, {
exp : "5"
}],
selectedDesignation : null,
filterDesignation : function() {
var designation = this.get('selectedDesignation.designation');
var filtered = this.get('arrangedContent').filterProperty('designation', designation);
this.set("filteredContent", filtered);
}.observes('selectedDesignation'),
designations : [{
designation : "Design",
id : 1
}, {
designation : "Writer",
id : 2
}, {
designation : "Script",
id : 3
}, {
designation : "Storyboard",
id : 4
}, {
designation : "Workbook",
id : 5
}],
actions : {
filterExperience : function() {
var experience = this.get('selectedExperience.exp');
var filtered = this.get('content').filterProperty('experience', experience);
this.set("filteredContent", filtered);
},
refresh : function() {
var refresh = this.get('content');
this.set("filteredContent", refresh);
}
},
filteredContent : function() {
var searchText = this.get('searchText'), regex = new RegExp(searchText, 'i');
return this.get('model').filter(function(item) {
var fullname = item.firstname + item.lastname;
return regex.test(fullname);
});
}.property('searchText', 'model')
});
As you can see, I have filterDesignation & filterExperience. But only filterExperience works as expected not the filterDesignation.
Moreover here is the HTML Template for that Controller:
<script type="text/x-handlebars" id="twod">
<div class="row">
<div class="span4">
<img src="/img/2DPipeline.jpg"/>
</div>
<div class="span4">
<h4>2D Roles</h4>
{{view Ember.Select
contentBinding="designations"
optionValuePath="content.id"
optionLabelPath="content.designation"
selectionBinding="selectedDesignation"
prompt="Please Select a Role"}}
{{view Ember.Select
contentBinding="experience"
optionValuePath="content.exp"
optionLabelPath="content.exp"
selectionBinding="selectedExperience"
prompt="Please Select Experience"}}
<br/>
<!-- <button {{action 'filter'}}>Filter By Designation</button>
<button {{action 'filterExperience'}}>Filter By Experience</button>
<button {{action 'refresh'}}>Refresh</button> --> </div>
<div class="span3">
<h4>People with Roles</h4>
{{input type="text" value=searchText placeholder="Search"}}
<div class="row">
<div class="span2">
<ul>
{{#each item in filteredContent}}
<li>{{#link-to 'twoduser' item}}{{item.firstname}} {{item.lastname}} {{/link-to}}</li>
{{/each}}
</ul>
</div>
<div class="row">
<div class="span3">
{{outlet}}
</div>
</div>
</div>
</div>
</div>
</script>
Here's the full JSBin as well.
What might be the issue?
Edit: The Search box in "Twod" Template doesn't work. Any ideas why?
The observer is working. Your JSON appears to contain additional whitespace. The designation value for each record has a space on the front and back of the value.
designation: " Design "
the filter is looking for
designation: "Design"
trimming your designation on the server should fix this up.
Additional issue(s):
Is filteredContent an array, or a computed property? You are blasting away your computed property on init and replacing the filteredContent computed property with an empty array
init : function() {
this.set('filteredContent', []);
},
or this?
filteredContent : function() {
var searchText = this.get('searchText'), regex = new RegExp(searchText, 'i');
return this.get('model').filter(function(item) {
var fullname = item.firstname + item.lastname;
return regex.test(fullname);
});
}.property('searchText', 'model')
Additionally you are filtering in multiple places, so one filter will totally blast away another filter. So I ripped out the observes for each one of those drop downs, and removed the init destroying the filteredContent array.
filteredContent : function() {
var designation = this.get('selectedDesignation.designation'),
hasDesignation = !Ember.isEmpty(designation),
experience = this.get('selectedExperience.exp'),
hasExperience = !Ember.isEmpty(experience),
searchText = this.get('searchText'),
hasSearch = !Ember.isEmpty(searchText),
regex = hasSearch ? new RegExp(searchText, 'i') : undefined;
return this.get('model').filter(function(item) {
var fullname = Em.get(item,'firstname') + Em.get(item,'lastname');
return (!hasDesignation || Em.get(item, 'designation') === designation) &&
(!hasExperience || Em.get(item, 'experience') === experience) &&
(!hasSearch || regex.test(fullname));
});
}.property('searchText', 'model', 'selectedExperience.exp', 'selectedDesignation.designation')
http://jsbin.com/aHiVIwU/27/edit
BTW
If you don't want it to show anything until they've filtered at least one thing, instead of setting filtered content to an empty array in the init, you would do that in the computed property, filteredContent, like so (before the filter):
if(!(hasDesignation|| hasExperience || hasSearch)) return [];
Ember isEmpty
Verifies that a value is `null` or an empty string, empty array,
or empty function.
Constrains the rules on `Ember.isNone` by returning false for empty
string and empty arrays.
```javascript
Ember.isEmpty(); // true
Ember.isEmpty(null); // true
Ember.isEmpty(undefined); // true
Ember.isEmpty(''); // true
Ember.isEmpty([]); // true
Ember.isEmpty('Adam Hawkins'); // false
Ember.isEmpty([0,1,2]); // false
```