I have a model, which will be related to a number of other models. Think of a stack overflow question, for example, where it is a question related to tags. The final Object might look as follows before a POST or a PUT:
{
id: 28329332,
title: "checkboxes that append to a model in Angular.js",
tags: [{
id: 5678,
name: "angularjs"
}, {
id: 890,
name: "JavaScript"
}]
}
So far, I have the following controller:
.controller('CreateQuestionCtrl',
function($scope, $location, Question, Tag) {
$scope.question = new Question();
$scope.page = 1;
$scope.getTags = function() {
Tag.query({ page: $scope.page }, function(data) {
$scope.tags = data;
}, function(err) {
// to do, error when they try to use a page that doesn't exist
})
};
$scope.create = function() {
$scope.question.$save(function(data) {
$location.path("/question/" + data.id);
});
};
$scope.$watch($scope.page, $scope.getTags);
}
)
So I display all of the tags, paginated, on the page. I want them to be able to select the given tags and append it to my model so that it can be saved.
How can I create a checkbox interface where it updates the $scope.question with the selected other models?
EDIT: think I might be part of the way there
<div class="checkbox" ng-repeat="tag in tags.objects">
<label><input
type="checkbox"
ng-change="setTag(tag.id)"
ng-model="tag"
> {{ tag.name }}
</div>
Then on the controller
$scope.setTag = function(id) {
Tag.get({id: id}, function(data) {
// don't know what now
})
}
Basically, it takes a directive to approach your goal Take a look at the plunker I wrote for you. As you can see, in the list of selected tags the text property of each tag is displayed, it means that the object structure is kept. In your case, you would bind the $scope.question.tags array as the collection attribute and each tag from the $scope.tags as the element attribute.
Here a codepen for multiple check-boxes bound to the same model.
HTML
<html ng-app="codePen" >
<head>
<meta charset="utf-8">
<title>AngularJS Multiple Checkboxes</title>
</head>
<body>
<div ng:controller="MainCtrl">
<label ng-repeat="tag in model.tags">
<input type="checkbox" ng-model="tag.enabled" ng-change="onChecked()"> {{tag.name}}
</label>
<p>tags: {{model.tags}}</p>
<p> checkCount: {{counter}} </p>
</body>
</html>
JS
var app = angular.module('codePen', []);
app.controller('MainCtrl', function($scope){
$scope.model = { id: 28329332,
title: "checkboxes that append to a model in Angular.js",
tags: [{
id: 5678,
name: "angularjs",
enabled: false
}, {
id: 890,
name: "JavaScript",
enabled: true
}]
};
$scope.counter = 0;
$scope.onChecked = function (){
$scope.counter++;
};
});
I found a great library called checklist-model worth mentioning if anyone is looking up this question. All I had to do was this, more or less:
<div class="checkbox" ng-repeat="tag in tags">
<label>
<input type="checkbox" checklist-model="question.tags" checklist-value="tags"> {{ tag.name }}
</label>
</div>
Found this on googling "directives for angular checkbox".
Related
Backstory: I want to display a little more information on an option element inside of a select box. The way I planned about doing this was to hover over an option and display more information about that option below, and this works! :)
The Problem: While this works in every other browser except IE (I tested this issue in IE11), however, it appears as though IE won't trigger the event at all. I tried different ng-{events} here and nothing appears to work. I want to know if there is a workaround for this, or possibly a different way of solving this problem. I created an example of the issue. Be sure to test it in IE11 (this is the browser I need it to work in unfortunately). Why IE WHYYY!!!? :(
Note I am looking for an angular solution. :)
(function(angular) {
'use strict';
angular.module('ngrepeatSelect', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.data = {
hovered: '',
model: null,
showExtraInformation: function (option) {
this.hovered = option.health;
},
clearExtraInformation: function () {
this.hovered = '';
},
availableOptions: [
{id: '1', name: 'Option A', health: 'Great Health :)'},
{id: '2', name: 'Option B', health: 'Bad Health :('},
{id: '3', name: 'Option C', health: 'Ok Health :|'}
]
};
}]);
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-select-ngrepeat-production</title>
<script src="//code.angularjs.org/snapshot/angular.min.js"></script>
<script src="app.js"></script>
<style>
select {height: 100px; width: 200px;}
</style>
</head>
<body ng-app="ngrepeatSelect">
<div ng-controller="ExampleController">
<form name="myForm">
<label for="repeatSelect"> Repeat select: </label>
<select multiple name="repeatSelect" id="repeatSelect" ng-model="data.model">
<option ng-repeat="option in data.availableOptions"
value="{{option.id}}"
ng-mouseover="data.showExtraInformation(option)"
ng-mouseout="data.clearExtraInformation()">{{option.name}}</option>
</select>
</form>
<hr>
<tt>model = {{data.model}}</tt><br/>
<tt>
hover = {{data.hovered}}
</tt>
</div>
</body>
</html>
In IE 11 blur event is triggered before mousedown , So check if present Element and target element are same then return or else do scroll FUNCTIONALITY something
Inside Blur Event
bind("blur", function(e) {
var tarElement = event.relatedTarget ? event.relatedTarget : nextElement;
if (tarElement.id === iElement.attr('id')) {
return;
} else {
CALL scroll
}
I have an answer...
Underlying Problem: After some reading, IE does not support events on the "option" element. For example, (click, mouseover, mouseout, change, blur, etc).
Based on JC Ford's response, I decided to solve this problem using checkboxes in angular material. I chose not to use a "material multiple select" since the behavior of the UI is not particularly what I or the client is expecting, however, if you wanted to go down that path, I did test it and it does work with these events...
Attached is my solution.
Note: the solution doesn't show the checkboxes, material doesn't want to show up here. Not sure why, but if you put it into your application, it works.
(function(angular) {
'use strict';
angular.module('MyApp', ['ngMaterial', 'ngMessages', 'material.svgAssetsCache'])
.controller('AppCtrl', function($scope) {
$scope.selected = [];
$scope.hovered = '';
$scope.model = null;
$scope.items = [{
id: '1',
name: 'Option A',
health: 'Great Health :)'
},
{
id: '2',
name: 'Option B',
health: 'Bad Health :('
},
{
id: '3',
name: 'Option C',
health: 'Ok Health :|'
}
];
$scope.showExtraInformation = function(option) {
$scope.hovered = option.health;
};
$scope.clearExtraInformation = function() {
$scope.hovered = '';
};
$scope.toggle = function(item, list) {
var idx = list.indexOf(item);
if (idx > -1) {
list.splice(idx, 1);
} else {
list.push(item);
}
};
$scope.exists = function(item, list) {
return list.indexOf(item) > -1;
};
$scope.isIndeterminate = function() {
return ($scope.selected.length !== 0 &&
$scope.selected.length !== $scope.items.length);
};
$scope.isChecked = function() {
return $scope.selected.length === $scope.items.length;
};
$scope.toggleAll = function() {
if ($scope.selected.length === $scope.items.length) {
$scope.selected = [];
} else if ($scope.selected.length === 0 || $scope.items.length > 0) {
$scope.selected = $scope.items.slice(0);
}
};
});
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-select-ngrepeat-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-route.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-messages.min.js"></script>
<script src="//s3-us-west-2.amazonaws.com/s.cdpn.io/t-114/svg-assets-cache.js"></script>
<script src="//cdn.gitcdn.link/cdn/angular/bower-material/v1.1.5/angular-material.js"></script>
<script src="app.js"></script>
</head>
<body>
<div ng-controller="AppCtrl" class="md-padding demo checkboxdemoSelectAll" ng-app="MyApp">
<fieldset class="demo-fieldset">
<legend class="demo-legend">Using md-checkbox with the 'indeterminate' attribute </legend>
<div layout="row" layout-wrap="" flex="">
<div flex-xs="" flex="50">
<md-checkbox aria-label="Select All" ng-checked="isChecked()" md-indeterminate="isIndeterminate()" ng-click="toggleAll()">
<span ng-if="isChecked()">Un-</span>Select All
</md-checkbox>
</div>
<div class="demo-select-all-checkboxes" flex="100" ng-repeat="item in items">
<md-checkbox ng-checked="exists(item, selected)" ng-click="toggle(item, selected)" ng-mouseover="showExtraInformation(item)" ng-mouseout="clearExtraInformation()">
{{ item.name }}
</md-checkbox>
</div>
</div>
</fieldset>
<hr>
<tt>model = {{selected}}</tt><br/>
<tt>
hover = {{hovered}}
</tt>
</div>
</body>
</html>
SUMMARYI have a list of brands and a list of products. I am using an ng-repeat to show the list of brands, and an ng-repeat with a filter to show the list of products within their respective brands. I want each brand and each product to have a button that shows more about that brand/product. All of these buttons should use the same function on the controller.
PROBLEMThe button that shows more about the brand also shows more about each of that brand's products, UNLESS (this is the weird part to me) I click the button of a product within that brand first, in which case it will work correctly.
CODEPlease see the Plunker here, and note that when you click on 'show type' on a brand, it also shows all the types of the products within that brand: http://plnkr.co/edit/gFnq3O3f0YYmBAB6dcwe?p=preview
HTML
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="style.css">
<script src="script.js"></script>
</head>
<body>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.3/angular.min.js"></script>
<script src="script.js"></script>
</body>
</html>
JAVASCRIPT
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if(brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
IMPORTANT NOTE: I realize I could add an attribute to the object (brand.show) and pass the object into the function, then change that attribute to true/false, but I don't want to do this because in my actual application, the button will show a form that edits the brand/product and submits the info to Firebase, and I don't want the object to have a 'show' attribute on it. I would rather not have to delete the 'show' attribute every time I want to edit the info in Firebase.
ng-repeat directive create own scope, when you do
this.show = !this.show
you create/change show property in current scope, if click brand button - for brand scope, that global for product, and when click in product button - for scope concrete product.
To avoid this, you should create this property before clicking button, for example with ng-init, like
ng-init="show=false;"
on element with `ng-repeat" directive
Sample
var app = angular.module('myApp', []);
app.controller('MyController', function($scope) {
$scope.brands = [{
name: 'Kewl',
type: 'Cereal'
}, {
name: 'Joku',
type: 'Toy'
}, {
name: 'Loko',
type: 'Couch'
}]
$scope.products = [{
name: 'Kewlio',
type: 'Sugar Cereal',
brand: 'Kewl'
}, {
name: 'Kewliano',
type: 'Healthy Cereal',
brand: 'Kewl'
}, {
name: 'Jokurino',
type: 'Rattle',
brand: 'Joku'
}, {
name: 'Lokonoko',
type: 'Recliner',
brand: 'Loko'
}, {
name: 'Lokoboko',
type: 'Love Seat',
brand: 'Loko'
}]
$scope.showType = function(item) {
this.show = !this.show;
}
$scope.filterProducts = function(brand) {
return function(value) {
if (brand) {
return value.brand === brand;
} else {
return true;
}
}
}
});
/* Styles go here */
h1 {
font-family: impact;
}
h2 {
font-family: arial;
color: blue;
margin-bottom: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.js"></script>
<div ng-app="myApp">
<div ng-controller="MyController as vm">
<div ng-repeat="brand in brands" ng-init="show=false">
<h1>
{{brand.name}}
</h1>
<button ng-click="showType(brand)">
Show Brand Type
</button>
<div ng-show="show">
{{brand.type}}
</div>
<div ng-repeat="product in products
| filter:filterProducts(brand.name)" ng-init="show=false">
<h2>
{{product.name}}
</h2>
<button ng-click="showType(product)">
Show Product Type
</button>
<div ng-show="show">
{{product.type}}
</div>
</div>
</div>
</div>
</div>
The easiest fix for this, if you don't mind putting temporary properties in your data is the following changes:
<div ng-show="product.show">
{{product.type}}
</div>
and
<div ng-show="brand.show">
{{brand.type}}
</div>
and then in your controller
$scope.showType = function(item) {
item.show = !item.show;
}
Alternatively, if you don't want to touch the object properties, you can create an $scope.shownTypes array and have your click either push the object into or remove the object from the shown array. THen you can check for the object's existence in the array and show or not show the type appropriately. Let me know if you need a sample of that.
Your show boolean attribute same for whole tree (is in same scope). Using angular directive with child scope scope:true in ng-repeat helps to isolate each show property. I have forked your plunker code:
http://plnkr.co/edit/cMSvyfeCQOnTKG8F4l55?p=preview
I am new to angular
in the following controller i need to access the object store in my html. But it is not working. Any help
(function () {
'use strict';
angular.module('app').controller('BookController', ['$scope', function ($scope) {
$scope.book = {
id: 1,
name: 'Harry Potter',
author: 'J. K. Rowling',
stores: [
{ id: 1, name: 'Barnes & Noble', quantity: 3 },
{ id: 2, name: 'Waterstones', quantity: 2 },
{ id: 3, name: 'Book Depository', quantity: 5 }
]
};
}]);
});
<div ng-controller="BookController">
{{book.stores}}
</div>
You need to first invoke your anonymous function first using () after the final closing bracket and before the final semi-colon so that the last line looks like this: })();.
You should define angular module first and then amend it with the angular component like controller, service , factory, directive, filters, etc.
angular.module('app', [])
then add ng-app="app" on your page.
Markup
<div ng-app="app" ng-controller="BookController">
{{book.stores}}
</div>
Plunkr Here
Update
If suppose you have multiple store inside the stores object, and you want to show them on the html, then for that you could ng-repeat directive. It will repeat each element on html
<div ng-repeat="s in book.stores">
<span>{{s.name}}</span>
<input type="text" ng-model="s.name" />
<input type="numeric" ng-model="s.quantity" />
</div>
Updated Plunkr
I'm looking for two things:
To push items in a nested array with Angularjs
To understand how it works exactly.
I've been looking for answers on differents previous topic but I didn't manage to come to a solution.
Actually, I want to use an Add Item button to push an item in a items array under a facture object.
Here is my controller:
PlasmaCrm.controller('FacturesSoloController', function($scope, $stateParams, Facture ) {
Facture.get({ id: $stateParams.factureId }, function(data) {
$scope.facture = data;
});
$scope.ajouterItem = function(index, item){
$scope.facture.items[index].item.push({
description: 'Test'
});
}
});
And here is my data structure (as returned by my API)
{
"id":10200,
"client_id":1,
"lead_id":1,
"courtedescription":"Description test",
"etat":"En attente",
"created_at":"2015-02-21 15:07:17",
"updated_at":"2015-02-21 15:07:17",
"items":[
{
"id":1,
"facture_id":10200,
"description":"Item num\u00e9ro 1",
"prix":"15.00",
"tps":"0.75",
"tvq":"1.50",
"grandtotal":"17.25",
"created_at":"2015-02-21 15:07:18",
"updated_at":"2015-02-21 15:07:18"
},
{
"id":2,
"facture_id":10200,
"description":"Deuxi\u00e8me item quoi",
"prix":"135.00",
"tps":"6.75",
"tvq":"13.47",
"grandtotal":"155.22",
"created_at":"2015-02-21 15:07:18",
"updated_at":"2015-02-21 15:07:18"
}
]
}
Of course my HTML contains a button:
<form ng-submit="ajouterItem(item)">
<button class="btn btn-primary">Ajouter un item</button>
</form>
Actually I got an error (undefined) when I press to button. What is wrong?
For those who are still looking for pushing data in the nested array can refer below example of Comments and Replies :
<!DOCTYPE html>
<html>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
<body>
<div ng-app="myApp" ng-controller="myCtrl">
<!--Comment section-->
<ul ng-repeat="comment in comments track by $index" style="background: skyblue; padding: 10px;">
<li>
<b>Comment {{$index}} : </b>
<br>
{{comment.comment}}
<!--Reply section-->
<ul ng-repeat="reply in comment.reply track by $index">
<li><i>Reply {{$index}} :</i><br>
{{reply.comment}}</li>
</ul>
<!--End reply section-->
<input type="text" ng-model="reply" placeholder=" Write your reply." />Reply
</li>
</ul>
<!--End comment section -->
<!--Post your comment-->
<b>New comment</b>
<input type="text" placeholder="Your comment" ng-model="comment" />
Post
</div>
<script>
var app = angular.module('myApp', []);
app.controller('myCtrl', function ($scope) {
//Comments object having reply oject
$scope.comments = [{ comment: 'hi', reply: [{ comment: 'hi inside commnet' }, { comment: 'hi inside commnet' }] }];
//push reply
$scope.insertReply = function (index, reply) {
$scope.comments[index].reply.push({ comment: reply });
}
//push commnet
$scope.newComment = function (comment) {
$scope.comments.push({ comment: comment, reply: [] });
}
});
</script>
</body>
</html>
Since there is no item property inside the items array objects, you cant push to it. You have to add:
$scope.facture.items[index].item = []
before you can push to it. Also check your functions parameters as Marc states in his comment. Since we can't see all of the markup it is unclear what is passed to the function, a simple console.log() will show you that ofcourse.
I found the answer, it was finaly simpler than I first thought:
$scope.ajouterItem = function(){
$scope.facture.items.push({
description: 'Test'
});
}
First, create a variable to fill, delete, and add items. Next, assign this variable to the array inside the model.
PlasmaCrm.controller('FacturesSoloController', function($scope, $stateParams, Facture )
{
$scope.items= [];
Facture.get({ id: $stateParams.factureId }, function(data) {
$scope.facture = data;
$scope.items = $scope.facture.items;
});
$scope.ajouterItem = function(item){
$scope.items.push(item);
$scope.facture.Items = $scope.items;
}
});
In this way, you can also edit the previous information and add new information. Since we first set "items". To remove the same as usual :
$scope.RemoveItem = function (index) {
$scope.facture.Items.splice(index, 1);
};
I have a data model persons which takes the following form:
personsInfo = {
name: Adam
dob: 31-FEB-1985
docs: [
{
docType: Drivers License,
number: 121212,
selected: false
id: 1
},
{
selected: true,
docType: None
},
{
docType: State ID,
number: 132345,
selected: false,
id: 2
}
]
}
In my markup I have defined the following to dynamically generate radio buttons.
<div ng-repeat="personDoc in personsInfo.docs">
<input type="radio" name="personDocs" ng-model="personDoc.selected" value=""/>
{{personDoc.docType}} <span ng-hide="personDoc.docType === 'None'">Number: {{personDoc.number}}</span>
</div>
I want to be able to check the documents which have selected as true on page load, and then depending on what the user selects save the selected flag in my personsInfo model.
My intent here is to send the personsInfo model back to another page.
If somebody could point me to a working fiddle it would be greatly appreciated!
Thanks!
You're almost there just missing the binding to show which document is selected. We'll add an object to the scope to represent the selected item, then bind the forms to that model.
JS
app.controller('...', function($scope) {
$scope.personInfo = { ... };
$scope.selectedDoc = {};
$scope.$watch('personInfo',function() {
$scope.selectedDoc = $scope.personInfo.docs[0];
});
});
HTML
<div ng-repeat='doc in personInfo.docs'>
<input type='radio' ng-model='selectedDoc' value='doc' /> {{doc.docType}}
</div>
<form>
<input type='text' ng-model='selectedDoc.number' />
...
</form>