Angular: How to bind to an entire object using ng-repeat - data-binding

I'm just beginning to experiment in Angular, and confused about how best to approach binding using ng-repeat. I basically get the point about ng-repeat creating a child scope. My problem is much more basic :) For html like this:
<div ng-controller="swatchCtrl" class="swatch-panel">
Swatches
<ul>
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
name="swatches"
ng-model="$parent.currentSwatch"
value="{{swatch}}"
>
<label class="swatch-label">
<div class="swatch-color" style="background-color: #{{swatch.hexvalue}};"></div
><span class="swatch-name">{{swatch.colorName}}</span>
</label>
</li>
</ul>
currentSwatch is:
<pre>{{currentSwatch | json}}</pre>
currentSwatchObj is:
<pre>{{currentSwatchObj | json}}</pre>
how do I tell this to fire??
swatchArray is:
<pre>{{swatchArray | json}}</pre>
</div>
and javascript like this:
function swatchCtrl($scope) {
$scope.swatchArray = [
{colorName:'Red', hexvalue: 'ff0000', selected: 'false'},
{colorName:'Green', hexvalue: '00ff00', selected: 'false'},
{colorName:'Blue', hexvalue: '0000ff', selected: 'false'}
];
$scope.currentSwatch = {};
}
http://jsfiddle.net/8VWnm/
I want to:
a) When the user clicks on a radio button, I want it to set both the colorName and the hexvalue properties of the currentSwatch object. Right now the binding seems to be giving me a stringified object from the array. How do watch the return of currentSwatch so I can parse it back to an available object? Simple, I know, but what am I missing?
b) When the user clicks on a radio button, I think I want that to set the value of the corresponding "selected" key in the original array to "true". Vice versa for unchecking. Let's say that only one swatch can ever be selected at a time in the palette. (I would like in theory to be able to iterate through the array later on, on the supposition that the different keys and values are likely to sometimes not be unique.)
This kinda stuff is super easy with jquery methods, but I'd like to learn the idiomatic angular way. Thanks in advance for any help.

http://jsfiddle.net/8VWnm/54/
Instead of listening to the ng-click event I would set the index of the selected element to a variable called "currentSwatchIndex"
<li ng-repeat="swatch in swatchArray" class="swatch">
<input
type="radio"
ng-model="$parent.currentSwatchIndex"
value="{{$index}}"
>
</li>
The you can $watch value changes of the currentSwatchIndex in your controller and set the selected swatch-Object and selection states in this $watch function:
$scope.$watch('currentSwatchIndex', function(newValue, oldValue) {
$scope.currentSwatchObj = $scope.swatchArray[newValue];
$scope.swatchArray[newValue].selected = true;
$scope.swatchArray[oldValue].selected = false;
});
Only knowing the currentSwatchIndex should be enough to identify the selected swatchObject. So probably you can get rid of the currentSwatchObj and the selected property of your swatchArray.
You can always get the selected swatch programmatically through a array access.

For future users that can come here to do the same in a select, you don't need use any index, the select must be done like this:
http://docs.angularjs.org/api/ng.directive:select

Related

Aurelia not displaying correct representation of array after removing item

I have a list of items in an array.
When i click the item in my view i am attempting to remove this item
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem(lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem(lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
console.log(_.includes(this.stack, lootItem)); //true
_.pull(this.stack, lootItem); //removes item and fails to update the ui
_.remove(this.stack, lootItem); //removes item and fails to update the ui
this.stack.shift(); //removes first item and updates the ui
}
Both .pull() and .remove() (using lodash) will remove the item in the array but not update the ui.
.shift() manages to remove an item from the array and is updating the UI.
Why is Aurelia not updating the UI despite me removing the item when using lodash?
addendum: it might be worth noting if i click the same item twice then _.includes is true the first time and then false the second time.
Aurelia can provide you with the index of the current item when inside a repeater using the $index variable. Then, simply use the built in array methods that ES2015 provides:
View
<div class="lootItem" repeat.for="lootItem of stack">
<div class="noselect" click.delegate="$parent.takeItem($index, lootItem)">
<i class="fa fa-diamond"></i> ${lootItem.value}
</div>
</div>
ViewModel
takeItem($index, lootItem){
this.eventAggregator.publish(new ItemTaken(lootItem));
this.stack.splice($index, 1);
}
I believe your problem lies in the scope of the variables.
_.pull and _.remove are returning a new instance of an array.
Try
this.stack = _.remove(this.stack, lootItem);
Using ES2015 Sets Yet another approach is to use the ES2015 Set data
structure. It can be initialized with an array of values, and the Set
prototype even provides a delete method for removing a specific value
...
However, the semantics of sets are different from regular arrays. By
definition, a set is a collection of unique values. Therefore, it
never contains duplicates. If you need a collection that allows for
duplicate values, sets are the wrong data structure.
https://blog.mariusschulz.com/2016/07/16/removing-elements-from-javascript-arrays
I am now using a Set for this and it allows me to use .add() and .delete(). I just need to remember that everything has to be unique (okay in my case here)
I'd still like to understand why manipulating the array though lodash doesn't work but i'll investigate that some other time.
Addendum
You can also use a prototype:
interface Array<T> {
remove(itemToRemove: T): Array<T>;
}
(<any>Array.prototype).remove = function (itemToRemove) {
const index = this.indexOf(itemToRemove);
if (index !== -1) {
this.splice(index, 1);
}
return this;
}

Correct way to disable multiple Textarea / checkbox depends on the dropdown box's Value

I'm looking for an effective way to enable / disable multiple controls (textarea, checkbox) through dropbox. I.e. Selecting item A in dropbox will disable certain controls, while selecting item B in dropbox will disable some other controls. Codes on how I approach with disabling textbox:
HTML:
<template name="Gender">
<input disabled={{shouldBeDisabled}} class="input" type="text"/>
</template>
<template name="DoB">
<textarea rows="3" cols="27" disabled={{shouldBeDisabled}}>purpose</textarea>
</template>
js:
Template.registerHelper("shouldBeDisabled", function() {
return true
});
Question 1: Do we require a registerHelper function for each individual control? In the code above it seems like the registerhelper will either disable or enable both control as oppose to individual, but having multiple registerhelper seems redundant.
Question 2: How can we control the value in registerHelper via dropbox (i.e. select)? I can return the value from the dropbox, is building a switch inside registerhelper the correct way and how does it incorporate into question 1?
Question 3: Is there a build-in function to add visual effect on disabled controls? (i.e.grey out)
The way I have done this in the past w/ Meteor and Blaze is to setup a event listener on the dropdown that sets a reactive var/session variable that I then read in a helper. The helper would return the string "disabled" depending on the value.
For instance (this is from memory...I don't have access to Meteor right now, and I have switched over to React/Mantra):
Template.MyComponent.oncreated(function() {
this.isDropdownDisabled = new ReactiveVar();
});
Template.MyComponent.events({
'change #myDropdown'(event) {
this.isDropdownDisabled.set($('#myDropdown').val() == 'Selected' ? true : false);
}
});
Template.MyComponent.helpers({
isDropdownSelected() {
return this.isDropdownDisabled.get() == true ? '' : 'disabled';
}
});
<select id="myDropdown">
<option value="Not Selected">Not Selected</option>
<option value="Selected">Select Me</option>
</select>
<input id="myDynamicallyDisabledInput" type="textbox" name="dnyamic1" {{isDropdownSelected}} />
That should roughly be correct. Basic idea is that you use a reactive var to store the "state" of the dropdown value, flip the "state" when that value changes, and use a help in the inputs to determine if the disabled attribute needs to be set or not. Since helper functions are reactive by default, swapping the state var will cause the template to re-evaluate any time the dropdown value changes.
Anyone can feel free to edit this response to clean up any bad code above, as again I haven't used Blaze in some time and I did this all from memory.

Can anyone help me understand why my arguments don't trigger when Session variable changes with React?

I'm trying to toggle between an active and inactive button based on whether or not the Session variable is true or false.
On a child component, I declare the Session variable:
choice(e){
e.preventDefault();
...
Session.set("upNext", true);
...
};
Then, on the parent component, I place this code in the render(){} function:
render(){
let upNext;
if(Session.get("upNext")){
upNext = <button className="ui right huge labeled icon primary button" onClick={this.nextPosition.bind(this)}><i className="right arrow icon"></i>Next</button>
} else{
upNext = <button className="ui right huge labeled icon primary button disabled" onClick={this.nextPosition.bind(this)}><i className="right arrow icon"></i>Next</button>
}
return(
<div className="ui container">
{upNext}
</div>
}
Finally, in the componentWillMount() method, I set the Session variable to false to reset before the next component loads.
The Session variable triggers, changes from false to true, but the code inside the first conditional (if Session.get is equal to true) never gets run. Never had this problem happen when working with Blaze, so there must be something here that I'm completely missing.
I have just decided to solve this problem with good old javascript and manipulated the className attribute after button click myself rather than depend on the Session variable.
Still, it's kind of strange as to why the Sessions are behaving weird with React.
You can add parts of your code that need to be reactive to within createContainer - https://guide.meteor.com/react.html#using-createContainer

How Do I Access a Property of bound Object Instance with Knockout

I am unable to figure out how to get to a property of a bound item. The following does not work when the .name() is there.
<span data-bind='text: selectedMenuSection().name()'></span>
Seems you can't access a property of a bound object in the data-bind. The same syntax works in a model function, MenuViewModel.showSelection(). I've tried every conceivable syntax with no success. Below is an excerpt of my code.
Html
MenuSections: <select data-bind="options: leftMenu, optionsText: 'name', value: selectedMenuSection, optionsCaption: 'Pick...'"> </select>
Selected: <span data-bind='text: selectedMenuSection().name()'></span>
<button data-bind="click: showSelection">Show Selected Info</button>
javascript
function MenuViewModel (menu) {
var self = this;
self.leftMenu = ko.observableArray(menu);
//Bound to Select
self.selectedMenuSection = ko.observable();
self.showSelection = function(){
alert(self.selectedMenuSection().name());
};
};
Is it not possible to get to a databound object property or have I just not figured out how to do it?
Can you try the following:
<span data-bind="with: selectedMenuSection">
<span data-bind="text: name"></span>
</span>
The first line makes sure that the inner span only exists if selectedMenuSection is not null or undefined (when you create self.selectedMenuSection, its value is undefined which can cause an error while trying to access the name property). The second line is your original binding. You don't need the () at the end, because you bind against the observable (you only need () to access the current value of the observable, or if you bind against a complex statement instead of directly to an observable).
What does a MenuSection look like? I'm assuming that it has a name property that is not an observable. Hans's answer is the correct way to solve your problem. Understand the javascript object and understanding what 'context' you are in when you write your knockout html is very important.
The with binding changes the context from the root element ( the object you passed into your ko.applyBindings) to the context of the selectedMenuSection. So inside of that 'with' your context ( $data in all cases ) is a MenuSection. If you are every curious about what object is in the context you can do something like this:
<div data-bind="with: selectMenuSection">
<pre data-bind="text: ko.toJSON($data)></pre>
<span data-bind="text: name"></span>
</div>
This will print out the current object context that you are in. You will then be able to see that you have a 'name' property available to you and whatever else is attached to a MenuSection.

How to bind to complex objects with radios and checkboxes in AngularJS?

Say we have a set of projects exposed via the Project service:
{ id: '123', name: 'Yeoman', watchers: '1233', ... }
{ id: '123', name: 'Grunt', watchers: '4343', ... }
Then, we have a form to choose your favorite project:
Select favorite project:
%label.radio(ng-repeat="project in Project.query()")
%input(type="radio" ng-model="data.favoriteProject" value="{{project.id}}") {{project.name}}
This sets choices.favoriteProject to the id value of the chosen project. Often, we need to access the related object, not just the id:
John's favorite project:
{{Project.get(data.favoriteProject).name}}
What I'm looking for is a way to bind the radios and checkboxes straight to the object itself, not the id, so we could do
John's favorite project:
{{data.favoriteProject.name}}
instead. This is possible with select directive via ng-options, but how can we do this with radios and checkboxes? I'd still like to use the ids for matching instead of references, if possible.
To clarify, here's an example what I'm looking for
Select favorite project:
%label.radio(ng-repeat="project in Project.query()")
%input(type="radio" ng-model="data.favoriteProject" value="{{project}}" ng-match="id") {{project.name}}
It says: "Please bind data.favoriteProject to the actual project object and use the id to check if they match (instead of references)".
[Update]
I've completely changed my answer after discovering the ngValue directive, which appears to be undocumented. It allows you to bind objects instead of just strings as values for ngModel on the radio button inputs.
<label ng-repeat="project in projects">
<input type="radio" ng-model="data.favoriteProject"
ng-value="project">{{project.name}}</input>
</label>
<p>Your favorite project is {{data.favoriteProject.name}}.</p>
This uses references to check rather than just IDs, but I think in most cases, this is what people will be looking for. If you do very strictly only want to match based on IDs, you can use the [Old Answer], below, or even better, just create a function--e.g. projectById(projectId) that you can use for looking up a project based on its ID.
I've updated the JSFiddle to demonstrate: http://jsfiddle.net/BinaryMuse/pj2GR/
[Old Answer]
Perhaps you can utilize the ng-change attribute of the radio button directive to achieve what you want. Consider this HTML:
<p>Select your favorite project:</p>
<label ng-repeat="project in projects">
<input type="radio" ng-model="data.favoriteProjectId" value="{{project.id}}"
ng-change="data.favoriteProject = project">
{{project.name}}
</input>
</label>
<p>Your favorite project is {{data.favoriteProject.name}}.</p>
You could also call a function inside ng-change, for example setfavoriteProject(project)--but I did not do that here for simplicity's sake.
Here is a working jsFiddle to demonstrate the technique: http://jsfiddle.net/BinaryMuse/pj2GR/7/
No ng-change needed (and I'm not sure, if it is a good practise to write inline-code like this. On the other hand angulars directives are not too far from it theirselves). Why not just do something like this (works with ng-repeat as well):
Fiddle:
http://jsfiddle.net/JohannesJo/VeZxh/3/
Code:
<body ng-app="app">
<div ng-controller='controller'>
<input type = "radio" ng-model = "oSelected" value = "{{aData[0]}}">
<input type = "radio" ng-model = "oSelected" value = "{{aData[1]}}">
<div>test: {{oSelected}}</div>
</div>
</body>
app = angular.module('app',[]);
app.controller('controller', function($scope){
$scope.aData = [
{o:1},
{o:2}
];
$scope.oSelected = {};
});
Edit: I maybe should mention that this doesn't work for checkboxes, as value will either be true or false. Also a shared model will lead to all checkboxes either being checked or unchecked at the same time.

Resources