Cannot update ui-select2 content from $scope.$watch - javascript

The simplified version of my code is as follows:
var app = angular.module("App", ['ui.select2']);
app.controller("MainController", function ($scope) {
$scope.select2Options1 = {
data: [{id: 0, text: "Foo"}, {id: 1, text: "Bar"}]
};
$scope.select2Options2 = {
data: []
};
$scope.$watch('chosen1', function (newVal, oldVal) {
if (newVal !== undefined && newVal !== null) {
console.log("Valid value selected: " + newVal);
$scope.select2Options2.data.push({id: 0, text: "FooBar"});
}
});
});
The markup:
<html>
<head>
<link rel="stylesheet" href="bower_components/select2/select2.css">
<script type="text/javascript" src="bower_components/jquery/jquery.js"></script>
<script type="text/javascript" src="bower_components/select2/select2.js"></script>
<script type="text/javascript" src="bower_components/angular/angular.js"></script>
<script type="text/javascript" src="bower_components/angular-ui-select2/src/select2.js"></script>
<script src="app.js"></script>
</head>
<body ng-app="App">
<input ng-controller="MainController" ng-model="chosen1" ui-select2="select2Options1" style="width:100px">
<input ng-controller="MainController" ng-model="chosen2" ui-select2="select2Options2" style="width:100px">
</body>
</html>
What I want to do, is to update the contents of a ui-select2 ($scope.select2Options2.data), when something gets chosen from another ui-select2 ($scope.select2Options1.data).
The problem is that the contents of the second ui-select2 is not updated no matter what I do in the handler of the $scope.$watch. If I move this $scope.select2Options2.data.push({id: 0, text: "FooBar"}); outside the watch handler, it works and the second ui-select2 gets populated. This means, that there must be something fundamentally wrong.
Any suggestions how to fix this?

I believe you're not using the module as recommended. Please read the documentation at https://github.com/angular-ui/ui-select2.
The way to enable options on a <select> in Angular is to use the ng-options directive. However, ui-select2 doesn't fully support this directive, and recommends using <option> tags instead. In your case that should be something like:
<select ui-select2 ng-model="select2" data-placeholder="Pick a number">
<option value=""></option>
<option ng-repeat="item in select2Options2" value="{{item.id}}">{{item.text}}</option>
</select>
Please note that, unlike using ng-options, the selected object stored via ng-model, in this case will be a string representation, not the actual object. This is a limitation when using ui-select2. So please be aware that what's stored in chosen2 will be the value of {{item.id}} (a string), not the {id: 0, text: "Foo"} object.
Last, why did it partly work in your case?
Reason is, you were using select2 internal functions to generate the options. This works like normal upon initialization, but there is no watch set on this select2options.data field (Select2 doesn't know anything about Angular). So whatever data was there upon initialization would show up, but any updates would not.

The root cause for the problem seemed to be the fact that I had defined MainController in the HTML twice. Wrapping everything inside a single div with ng-controller="MainController" fixed the problem.

Related

Knockoutjs error: You cannot apply bindings multiple times to the same element

I'm trying to automatically populate an element in an existing web page that I can't change and that page uses Knockoutjs. The input element looks like this more or less:
<input maxlength="8" id="xxx" data-bind="textInput: otcInput" type="tel">
Then I use Knockoutjs to attempt to unbind the textInput and populate the input element dynamically with whatever value I need, so I do:
ko.cleanNode($('#xxx'));
ko.applyBindings({
otcInput: ko.observable("123") // populate myself
});
However, this leads to the error You cannot apply bindings multiple times to the same element ... the question is why? I'm already cleaning the node ... or am I not? is there a way using knockoutjs to see whether there are dangling bindings or leftovers that get in the way while trying to execute my "overriding" ko.applyBindings?
I have also tried other ways to set the input value via JQuery sendkeys plugin without success i.e.
$('#xxx').sendkeys('123'); // nothing happens
I also tried:
$('#xxx').unbind();
$('#xxx').off();
$('#xxx').sendkeys('123'); // but again nothing happens
You're passing a jQuery object to cleanNode. Just like with applyBindings, it has to be a DOM element, not a jQuery object. So:
ko.cleanNode($('#xxx')[0]);
// -------------------^^^
Example — this fails:
ko.applyBindings({
foo: ko.observable("one")
}, $("#test")[0]);
ko.cleanNode($("#test"));
ko.applyBindings({
foo: ko.observable("two")
}, $("#test")[0]);
<div id="test">
<div data-bind="text: foo"></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
...but this (with the [0]) works:
ko.applyBindings({
foo: ko.observable("one")
}, $("#test")[0]);
ko.cleanNode($("#test")[0]);
ko.applyBindings({
foo: ko.observable("two")
}, $("#test")[0]);
<div id="test">
<div data-bind="text: foo"></div>
</div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
I have also tried other ways to set the input value
If that's your goal, you don't have to muck with the bindings (which probably would have undesirable effects), just:
$("#xxx").val("new value").trigger("change");
The trigger("change") is necessary to get KO to see the change and update the observable. (Or as it's a textInput binding, you might use input rather than change.)
Example — this fails:
// The previously-bound stuff:
var vm = {
foo: ko.observable("foo")
};
ko.applyBindings(vm, document.body);
// Prove the observable and input are in sync:
console.log("check1", vm.foo(), $("#test").val());
// Update the field
$("#test").val("updated").trigger("change");
// Prove the observable and input are still in sync:
console.log("check2", vm.foo(), $("#test").val());
<input id="test" type="text" data-bind="textInput: foo">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>

ng-change function is not being called and no error?

I am working with a select box in angular and it appears like the ng-change function is never being called. This is the code for the select:
<select
data-ng-options="choice.id as choice.singular for choice in model.doseUnitChoices"
data-ng-model="ucumId"
data-ng-change="updateForm()"
class="form-control input-sm">
<option value=""></option>
</select>
I have simplified the code in the controler function to do the minimum needed:
updateForm: function(){
console.log('test');
},
So far everything works like normal, the drop down shows all the available options, but when I select any option in the dropdown it immediately changes back to the last option, which may mean something, and the change function is never called and no errors appear in the console? Any ideas on how I can debug this issue and find out whats wrong?
UPDATE: interestingly I discovered while inspecting the element on the page is that actually all of the select elements are listed as selected, maybe there is an issue with the data being used as the model? Looking into it
I discovered that the issue was being caused by two problems outside the scope of the question:
ucumId didn't exist when this select was being called, which I guess caused all the select options to be set as selected somehow?
because the above error was causing all the options to be 'selected', no matter what i did in the select it never updated the values so no change was occurring?
Anyway making sure 'ucumId' existed fixed the issue I was having,
I just tried to duplicate it and with the minimum amount of code, it works fine.
<html>
<body ng-app="app" ng-controller="myController">
<select
data-ng-options="choice as choice.label for choice in model.doseUnitChoices"
data-ng-model="ucumId"
data-ng-change="updateForm()"
class="form-control input-sm">
<option value=""></option>
</select>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular.min.js "></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-animate.min.js "></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.5/angular-aria.min.js "></script>
<script>
angular.module("app",[]).controller("myController", myController)
function myController($scope) {
$scope.ucumId = "";
$scope.updateForm = function(){
console.log('test');
};
$scope.model = {}
$scope.model.doseUnitChoices = [
{id: 1, label: "one" },
{id:2, label: "two" }
]
}
</script>
</body>
</html>
I suspect that either there's a small typo somewhere, updateForm is redefined somewhere or your controller is getting redefined somewhere. I'd suggest pairing down the actual code and removing things piece by piece until you get to a point where it's working or something like that.

Iterate through elements generated with ng-repeat in AngularJS

my app.js:
var app = angular.module('factory', []);
app.controller('Ctrl', function($scope){
$scope.users =[
{name: 'jimbo', info: ''},
{name: 'bobby', info: ''}
];
$scope.addAllInfos = function(){
};
});
my index.html:
<!DOCTYPE html>
<html ng-app="factory">
<head>
<script type="text/javascript" src="angular.min.js"></script>
<script type="text/javascript" src="app.js"></script>
</head>
<body ng-controller="Ctrl">
<div ng-repeat="user in users">
Name: <input value="{{user.name}}"/><br>
Tell me sth: <input/><br><br><br>
</div>
<button ng-click="addAllInfos()">add all infos</button>
</body>
When clicking on "Add all infos" i want to iterate through all my users that i have generated by using ng-repeat and save the input of every "Tell me sth" input in the info-variable of the object.
How can I do so using Angular?
Fiddle
You just need to bind every repeated input to user.info using ngModel directive:
Tell me sth: <input type="text" ng-model="user.info" />
Demo: https://jsfiddle.net/xsakgy6b/2/
Note, that in this case addAllInfos function may become redundant as two-way data binding automatically populates user objects for you.
I think what you're looking for is the ng-model attribute. You should use ng-model on your input fields as well, instead of putting the value into the value attribute. This will allow you to take full control of two-way binding in Angular.
Then you would not really need the addAllInfos function, Angular will keep track of the values and properties of each object in your repeater. The code would look like this.
<div ng-repeat="user in users">
Name: <input type="text" ng-model="user.name"/><br>
Tell me sth: <input type="text" ng-model="user.info"/><br><br><br>
</div>
Here's my updated fiddle
Your first problem is your addAllInfos() function.
You do not have your addAllInfos() function available to the view / html. You need to expose the function, either through $scope, or returned via the controller class.
You can do that like so:
$scope.addAllInfos = function() { // code here };
This will allow you to call a function from the ng-click event on your .

Initializing select with AngularJS and ng-repeat

I'm trying to get select-box to start off with a pre-filled option using ng-repeat with AngularJS 1.1.5. Instead the select always starts out with nothing selected. It also has an empty option, which I don't want. I think that there is a side effect of nothing being selected.
I can get this working using ng-options instead of ng-repeat, but I want to use ng-repeat for this case. Although my narrowed down example doesn't show it, I also want to set the title attribute of each option, and there is no way to do that using ng-options, as far as I know.
I don't think this is related to the common AngularJs scope/prototypical inheritance issue. At least I don't see anything obvious when inspecting in Batarang. Plus, when you pick an option in the select with the UI, the model does update correctly.
Here's the HTML:
<body ng-app ng-controller="AppCtrl">
<div>
Operator is: {{filterCondition.operator}}
</div>
<select ng-model="filterCondition.operator">
<option
ng-repeat="operator in operators"
value="{{operator.value}}"
>
{{operator.displayName}}
</option>
</select>
</body>
And the JavaScript:
function AppCtrl($scope) {
$scope.filterCondition={
operator: 'eq'
}
$scope.operators = [
{value: 'eq', displayName: 'equals'},
{value: 'neq', displayName: 'not equal'}
]
}
JS Fiddle for this : http://jsfiddle.net/coverbeck/FxM3B/2/
OK. If you don't want to use the correct way ng-options, you can add ng-selected attribute with a condition check logic for the option directive to to make the pre-select work.
<select ng-model="filterCondition.operator">
<option ng-selected="{{operator.value == filterCondition.operator}}"
ng-repeat="operator in operators"
value="{{operator.value}}">
{{operator.displayName}}
</option>
</select>
Working Demo
For the select tag, angular provides the ng-options directive. It gives you the specific framework to set up options and set a default. Here is the updated fiddle using ng-options that works as expected: http://jsfiddle.net/FxM3B/4/
Updated HTML (code stays the same)
<body ng-app ng-controller="AppCtrl">
<div>Operator is: {{filterCondition.operator}}</div>
<select ng-model="filterCondition.operator" ng-options="operator.value as operator.displayName for operator in operators">
</select>
</body>
The fact that angular is injecting an empty option element to the select is that the model object binded to it by default comes with an empty value in when initialized.
If you want to select a default option then you can probably can set it on the scope in the controller
$scope.filterCondition.operator = "your value here";
If you want to an empty option placeholder, this works for me
<select ng-model="filterCondition.operator" ng-options="operator.id as operator.name for operator in operators">
<option value="">Choose Operator</option>
</select>
Thanks to TheSharpieOne for pointing out the ng-selected option. If that had been posted as an answer rather than as a comment, I would have made that the correct answer.
Here's a working JSFiddle: http://jsfiddle.net/coverbeck/FxM3B/5/.
I also updated the fiddle to use the title attribute, which I had left out in my original post, since it wasn't the cause of the problem (but it is the reason I want to use ng-repeat instead of ng-options).
HTML:
<body ng-app ng-controller="AppCtrl">
<div>Operator is: {{filterCondition.operator}}</div>
<select ng-model="filterCondition.operator">
<option ng-repeat="operator in operators" title="{{operator.title}}" ng-selected="{{operator.value == filterCondition.operator}}" value="{{operator.value}}">{{operator.displayName}}</option>
</select>
</body>
JS:
function AppCtrl($scope) {
$scope.filterCondition={
operator: 'eq'
}
$scope.operators = [
{value: 'eq', displayName: 'equals', title: 'The equals operator does blah, blah'},
{value: 'neq', displayName: 'not equal', title: 'The not equals operator does yada yada'}
]
}
As suggested you need to use ng-options and unfortunately I believe you need to reference the array element for a default (unless the array is an array of strings).
http://jsfiddle.net/FxM3B/3/
The JavaScript:
function AppCtrl($scope) {
$scope.operators = [
{value: 'eq', displayName: 'equals'},
{value: 'neq', displayName: 'not equal'}
]
$scope.filterCondition={
operator: $scope.operators[0]
}
}
The HTML:
<body ng-app ng-controller="AppCtrl">
<div>Operator is: {{filterCondition.operator.value}}</div>
<select ng-model="filterCondition.operator" ng-options="operator.displayName for operator in operators">
</select>
</body>
If you are using md-select and ng-repeat ing md-option from angular material then you can add ng-model-options="{trackBy: '$value.id'}" to the md-select tag ash shown in this pen
Code:
<md-select ng-model="user" style="min-width: 200px;" ng-model-options="{trackBy: '$value.id'}">
<md-select-label>{{ user ? user.name : 'Assign to user' }}</md-select-label>
<md-option ng-value="user" ng-repeat="user in users">{{user.name}}</md-option>
</md-select>

jQuery templates plugin: how to create two-way binding?

I started using jQuery templates plugin (the one Microsoft created), but now I face this problem: the template is for a bunch of forms bound to an array of objects; when I change something on one of the forms, I want the bound object to update and I can't figure out how to automate that.
Here's a simple example (real life template and object are much more complex) :
<!-- Template -->
<script type="text/html" id="tmplTest">
<input type="text" value="${textvalue}"/>
</script>
<!-- object to bind -->
<script type="text/javascript">
var obj = [{textvalue : "text1"},{textvalue : "text2"}]
jQuery("#tmplTest").tmpl(obj)
</script>
This will populate two textboxes, each bound to a value from corresponding object. Now, if I change a value in one of the textboxes, I need to update corresponding data object's value. Any idea how to do that?
jQuery template doesn't actually implement two-way data binding, but another Microsoft developed jQuery plugin does.
This Scott Guthrie post actually covers both the tmpl plug in and Data Linking plugin. Scroll down to "Support for Client Data-Linking" where Scott goes into detail on how the Data Linking plug in works.
However, for two way data binding, i find the knockoutjs extension to be much better and cleaner. The declarative syntax keeps the markup clean and the custom data binding overrides allow for a multitude of applications. Finally the mapping plugin is pretty great for processing JSON from the server into binding. Finally knockoutjs also has bindings based on tmpl plugin as well.
Good luck with your problem.
EDIT: updated Code Example
Scrips required:
<script src="/Scripts/jquery-1.5.0.min.js" type="text/javascript"></script>
<script src="/Scripts/jquery.tmpl.js" type="text/javascript"></script>
<script src="/Scripts/knockout.js" type="text/javascript"></script>
<script src="/Scripts/knockout.mapping.js" type="text/javascript"></script>
Then here is the meat and potatoes
<!-- div for loading the template -->
<div data-bind='template: { name: "tmplTest", foreach: viewModel.list }'>
</div>
<!-- your template -->
<script type='text/html' id='tmplTest'>
<div>
<span data-bind='text: textvalue, uniqueName: true'></span>
<input data-bind='value: textvalue, uniqueName: true, valueUpdate:"afterkeydown"'/>
</div>
</script>
<script type='text/javascript'>
var viewModel = ko.mapping.fromJS(
{
list:[ { textvalue: "text1" },
{ textvalue: "text2"} ]
});
$(function() {
ko.applyBindings(viewModel);
});
</script>
You could write your own data link. Note: I needed the index to make this work, so I added your data into an array, and put an each in the template. If someone knows how to get the index without using an each please add :) jsfiddle link
<script type="text/html" id="tmplTest">
{{each inputs}}
<input type="text" class="datalink" data-key="textvalue" data-index="${$index}" value="${$value.textvalue}"/>
{{/each}}
</script>
<div id="output"></div>
$.extend(window, {
data:[
{inputs: [{textvalue:"text1"},{textvalue:"text2"}]}
]
});
$('#output').append($("#tmplTest").tmpl(window.data));
$('#output .datalink').live('change', function(){ // update object on change
var $this = $(this),
d = $this.data();
window.data[0].inputs[d.index*1][d.key] = $this.val();
console.log(window.data[0].inputs);
});

Categories

Resources