knockout - cannot apply bindings multiple times to the same element - javascript

I need to fill my trip table with data from two sources: from server using GET when the page loads (to have user's archive trips) and from observable values (when a user adds a new trip). Can I somehow merge those two scripts so that they apply bindings only once? Right now I get an error: You cannot apply bindings multiple times to the same element
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">Add a trip</h3>
</div>
<div class="panel-body">
<div class="form-group">
<label for="from">From</label>
<input type="text" class="form-control" id="from" name="from" placeholder="From" data-bind="value: from">
</div>
<div class="form-group">
<label for="to">To</label>
<input type="text" class="form-control" id="to" name="to" placeholder="To" data-bind="value: to">
</div>
<a class="btn btn-primary btn-lg" role="button" data-bind="click: add()" >Add</a>
</div>
</div>
<div class="panel panel-default">
<div class=panel-heading>Your trips</div>
<table class=table>
<thead>
<tr>
<th>From</th>
<th>To</th>
</tr>
</thead>
<tbody data-bind="foreach: records">
<tr>
<td data-bind="text: from"></td>
<td data-bind="text: to"></td>
</tr>
</tbody>
</table>
</div>
<script type="text/javascript" src="js/knockout-3.4.0.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js"></script>
<script type="text/javascript">
var AppViewModel = function() {
this.from = ko.observable();
this.to = ko.observable();
this.records = ko.observableArray();
};
var model = new AppViewModel();
model.add = function() {
model.records.push({
from: model.from(),
to: model.to()
});
//sending data to server
var data =
{
from : this.from(), to : this.to(), date : this.date(), price : this.price(), freeSeats : this.freeSeats()
}
alert(data);
$.post("/data", data, function(response)
{
})
}
ko.applyBindings(model);
</script>
<script>
function tripModel() {
this.records = ko.observableArray([]);
$.getJSON("/usersTrips", function(data) {
self.records(data);
})
}
ko.applyBindings(new tripModel());
</script>

Give the relevant elements IDs and then apply the models to only those DOM elements. For example,
Html:
<div id="add-trip" class="panel panel-default">
<div id="your-trips" class="panel panel-default">
And the binding:
ko.applyBindings(model, document.getElementById("add-trip"));
ko.applyBindings(new tripModel(), document.getElementById("your-trips"));
JSFiddle Example:
https://jsfiddle.net/hellobrett/49xaqj46/1/
JSFiddle example going the other direction:
https://jsfiddle.net/hellobrett/49xaqj46/2/
Reference:
http://knockoutjs.com/documentation/observables.html
In case you’re wondering what the parameters to ko.applyBindings do,
The first parameter says what view model object you want to use with the declarative bindings it activates
Optionally, you can pass a second parameter to define which part of the document you want to search for data-bind attributes. For example, ko.applyBindings(myViewModel, document.getElementById('someElementId')). This restricts the activation to the element with ID someElementId and its descendants, which is useful if you want to have multiple view models and associate each with a different region of the page.

Related

$filter ui-grid using search fields

At the moment I have a ui-grid with a full name and registration columns. I also have search fields for both. If user searches for registration, the table is updated 'on the fly'. My issue is that if the user enters both to narrow down the search e.g. registration and name at the same time. The table only reacts on a single filter input.
This is part of my code responsible for watching the field vm.model.employeeName and vm.model.registrationName which are the search fields. vm.resultData is an array of rows to be inserted into vm.gridOptions.data (the ui-grid).
vm.watchFilter = function() {
if (vm.model.employeeName || vm.model.registrationNumber) {
if (vm.model.employeeName) {
vm.updatedResultData = $filter('filter')(vm.resultData, {employeeFullName: vm.model.employeeName});
}
if(vm.model.registrationNumber) {
vm.updatedResultData = $filter('filter')(vm.resultData, {currentVehicleRegistration: vm.model.registrationNumber});
}
vm.gridOptions.data = vm.updatedResultData;
} else {
vm.gridOptions.data = vm.resultData;
}
vm.numberOfRecords = vm.gridOptions.data.length;
};
$scope.$watch('vm.model.employeeName', vm.watchFilter);
$scope.$watch('vm.model.registrationNumber', vm.watchFilter);
How do I combine the 2 to get narrowed down search? I've spent hours on this...
I don't want to end up with if statements for every possible search as I might add an extra search field in the future. Also if a user decides to enter reg first and then name.....
I hope this will help you. Thank you.
var app = angular.module('app', ['ngMessages']);
app.controller('pageCtrl', ['$scope', '$http', function($scope, $http) {
$scope.names = [{"Name":"Mark","Number":"10000"},
{"Name":"Steve","Number":"20000"},
{"Name":"Bill","Number":"30000"},
{"Name":"John","Number":"40000"},
{"Name":"Luis","Number":"50000"}];
$scope.search = function(){
$scope.criteria = angular.copy($scope.criteria1);
}
}]);
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.8/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular-messages.min.js"></script>
<div ng-app="app" class="container" >
<h1>Filter Name & Registeration Number</h1>
<hr />
<form ng-controller="pageCtrl" ng-init='getData()'>
<div class='form-group'>
<div class='row'>
<div class="right-inner-addon col-md-4 ">
<input type="search" ng-model='model.Name' class="form-control" placeholder="Name">
</div>
<div class="right-inner-addon col-md-4 ">
<input type="search" ng-model='model.Number' class="form-control" placeholder="Registeration Number">
</div>
</div>
</div>
<table class="table table-bordered" >
<tr>
<th>Name</th>
<th>Registeration Number</th>
<tr>
<tr ng-repeat="item in filtered = (names | filter : model)">
<td>{{item.Name}}</td>
<td>{{item.Number}}</td>
<tr>
</table>
</form>
</div>

Pushing object to observableArray

New to knockoutjs and I was following the tutorial on Loading and saving data, but I am having a little trouble. When I push my object to an array the array is empty. I am also using a form. Here is my code,
function Quiz(data) {
this.quiz_name = ko.observable(data.newQuizName);
this.quiz_type = ko.observable(data.newQuizType);
}
function QuizViewModel() {
var self = this;
self.quizzes = ko.observableArray([]);
self.newQuizName = ko.observable();
self.newQuizType = ko.observable();
self.addQuiz = function () {
self.quizzes.push(new Quiz({quiz_name: this.newQuizName(), quiz_type: this.newQuizType()}))
console.log(ko.toJSON(self.quizzes));
};
}
ko.applyBindings(new QuizViewModel());
and this is my HTML
<form name="quizzes" id="new-form-quizzes" data-bind="submit: addQuiz" style="display:none">
<div class="form-group">
<label for="quiz-name">Quiz Name</label>
<input type="text" class="form-control" id="quiz-name" aria-describedby="quiz name"
data-bind="value: newQuizName"
placeholder="Quize Name"/>
</div>
<div class="form-group">
<label for="quiz-type">Quiz Type</label>
<input type="text"
class="form-control"
id="quiz-type"
data-bind="value: newQuizType"
placeholder="Quiz Type"/>
</div>
<button type="submit">Save</button>
</form>
Not sure what I am doing wrong as both newQuizName and newQuizType do have values. Any help would be much appreciated.
You should pass an object (or an array) containing observables as an argument to ko.toJSON. Not the observable itself. So, you need to change your code to:
ko.toJSON(self.quizzes());
ko.toJSON internally calls ko.toJS. The latter method goes through the object and converts each observable to the value of that observable.
After making this change you'll find that there's another problem. The newly added Quiz object has undefined properties. This is because you are passing an object with quiz_name and quiz_type to the Quiz constructor function. But you are accessing newQuizName property from the data parameter. So change your code to:
function Quiz(data) {
this.quiz_name = ko.observable(data.quiz_name); // these 2 props must be changed
this.quiz_type = ko.observable(data.quiz_type);
}
function QuizViewModel() {
var self = this;
self.quizzes = ko.observableArray([]);
self.newQuizName = ko.observable();
self.newQuizType = ko.observable();
self.addQuiz = function() {
self.quizzes.push(new Quiz({
quiz_name: this.newQuizName(),
quiz_type: this.newQuizType()
}));
console.log(ko.toJSON(self.quizzes()));
};
}
ko.applyBindings(new QuizViewModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<form name="quizzes" id="new-form-quizzes" data-bind="submit: addQuiz">
<div class="form-group">
<label for="quiz-name">Quiz Name</label>
<input type="text" class="form-control" id="quiz-name" aria-describedby="quiz name" data-bind="value: newQuizName" placeholder="Quize Name" />
</div>
<div class="form-group">
<label for="quiz-type">Quiz Type</label>
<input type="text" class="form-control" id="quiz-type" data-bind="value: newQuizType" placeholder="Quiz Type" />
</div>
<button type="submit">Save</button>
</form>
<!--Table to display the added quizzes-->
<table data-bind="if: quizzes() && quizzes().length > 0">
<thead>
<th>Quiz Name</th>
<th>Quiz Type </th>
</thead>
<tbody data-bind="foreach: quizzes">
<tr>
<td data-bind="text:quiz_name"></td>
<td data-bind="text:quiz_type"></td>
</tr>
</tbody>
</table>

Autocomplete input field not working in bootstrap tabs

I am working on a web development project and I am using bootstrap tabs there. I am dynamically creating them using add and remove input field section, according to that number of inputs I am creating the tabs in the same page using ajax.
In that tabs I have the same form with same input fields. There is a input field with autocomplete function in that form. I am taking data from mysql to that field. My problem is autocomplete is only working in the first tab not in the others. Autocomplete field label is Responsible
This is my function.
function autocompletT() {
var min_length = 0;
var keyword = $('#responsible_id').val();
if (keyword.length >= min_length) {
$.ajax({
url: 'ajax_refresh2.php',
type: 'POST',
data: {keyword:keyword},
success:function(data){
$('#responsible_id_list').show();
$('#responsible_id_list').html(data);
$('#responsible_id_list li').click(function() {
var txx = $(this).text();
var tcust = $(this).val();
});
}
});
} else {
$('#responsible_id_list').hide();
}
}
// set_item : this function will be executed when we select an item
function set_item(item) {
// change input value
$('#responsible_id').val(item);
// hide proposition list
$('#responsible_id_list').hide();
}
Portion of My code
<div class="col-md-6 entire_div">
<div class="panel with-nav-tabs panel-primary">
<div class="panel-heading">
<ul class="nav nav-tabs">
<?php
for ($row = 0; $row < $agenda_size; $row++) {
$p_agenda = $mAgenda[$row];
$row_plus = $row + 1;
?>
<li><?php echo $p_agenda; ?></li>
<?php
}
?>
</ul>
</div>
<div class="panel-body">
<div class="tab-content">
<?php
for ($row = 0; $row < $agenda_size; $row++) {
$p_agenda = $mAgenda[$row];
$row_plus = $row + 1;
?>
<div class="tab-pane fade" id="<?php echo $row_plus;?>">
<!-- form -->
<form class="ws-validate">
<div class="form-group">
<label for="responsible_id" class="control-label">Responsible</label>
<input type="text" class="form-control" id="responsible_id" name="zip" placeholder="Select Responsible Person" onkeyup="autocompletT()" >
<ul id="responsible_id_list"></ul>
</div>
<div class="form-group">
<button id="submit_btn" class="btn btn-primary">Add</button>
<!-- <input id="submit_btn" type="submit" value="Add"> -->
</div>
</form>
<div class="row">
<div class="col-md-12">
<div class="table-responsive">
<table id="mytable" class="table table-bordred table-striped">
<thead>
<th><input type="checkbox" id="checkall" /></th>
<th>Action</th>
<th>Start Date</th>
<th>Due Date</th>
<th>Status</th>
<th>Responsible</th>
</thead>
<tbody>
</tbody>
</table>
<div class="delete_b_class">
<button id="delete_row" class="btn btn-danger">Delete Row</button>
</div>
</div>
</div>
</div>
<div class="submit_all_div">
<input class="submit_all_b" id="submit" onclick="myDataSendFunction()" type="button" value="Submit">
</div>
</div>
<?php
}
?>
</div>
</div>
</div>
</div>
ids are unique
Each element can have only one id
Each page can have only one element with that id
classes are NOT unique
You can use the same class on multiple elements.
You can use multiple classes on the same element.
Probably, you are creating so many #responsible_id and #responsible_id_list with your php code.
So, as IDs must be unique. use the a unique ID for each search field (which is responsible_id ) and list (which is responsible_id_list ) here.
You can get a idea from here:- remove onkeyup="autocompletT" function from the input and add jquery event like this. then find the next responsible_id_list then populate list and append list to it.
$('.responsible_id').keyup(function(){
var min_length = 0;
var keyword = $(this).val() ;
///others codes
//say
var list='';
$(this).next('.responsible_id_list').html(list);
})
You might need to re-initialize the autocomplete on the inputs every time. Go through this question to get a braoder picture. This should solve your concerns

creating textbox element dynamically and bind different model

I am working in angular js application, where i need to create textbox with buttons dynamically that means
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<input type="text" name="ndc9" class="form-control txtBoxEdit" ng-model="ndc9">
</div>
<div class="col-sm-4 type7 " style="font-size:14px;">
<div style="padding-top:20px; display:block">
<span class="red" id="delete" ng-class="{'disabled' : 'true'}">Delete</span> <span>Cancel </span> <span id="addRow" style="cursor:pointer" ng-click="ndcCheck(0)">Add </span>
</div>
</div>
this will create below one
i will enter some value in above textbox and click add ,it needs to be created in next line with same set of controls that means (textbox with above 3 buttons need to be created again with the entered value).
Entering 123 in first textbox and click add will create new textbox with delete,cancel,add button with entered value.
Again am adding new value 243 then again it needs to create new textbox down to next line with the entered value (and also the same controls).
finally i want to get all the entered values. how can i achieve this in angular js
You could use ng-repeat with an associative array. Add Would basically push the model value to an array and and also an empty object in the array.
<div ng-repeat ="ndc in NDCarray">
<div class="col-sm-4 type7" style="font-size:14px;">
<div style="margin-bottom:5px;">NDC9</div>
<input type="text" name="ndc9" class="form-control txtBoxEdit" ng-model="ndc.val">
</div>
</div>
<div class="col-sm-4 type7 " style="font-size:14px;">
<div style="padding-top:20px; display:block">
<span class="red" id="delete" ng-class="{'disabled' : 'true'}" ng-click="NDCdelete($index)">Delete</span>
<span>Cancel </span>
<span id="addRow" style="cursor:pointer" ng-click="NDCadd ()">Add </span>
</div>
</div>
</div>
In the controller:
$scope.NDCarray = [{val: ''}];
$scope.NDCadd = function() {
$scope.NDCarray.unshift(
{val: ''}
);
};
$scope.NDCdelete = function(index) {
$scope.NDCarray.splice(index, 1);
};
Plunker: https://plnkr.co/edit/3lklQ6ADn9gArCDYw2Op?p=preview
Hope this helps!!
<html ng-app="exampleApp">
<head>
<title>Directives</title>
<meta charset="utf-8">
<script src="angular.min.js"></script>
<script type="text/javascript">
angular.module('exampleApp', [])
.controller('defaultCtrl', function () {
vm = this;
vm.numbers = [1, 2, 3];
vm.add = function (number) {
vm.numbers.push(number);
}
vm.remove = function (number) {
var index = vm.numbers.indexOf(number);
if(index>-1){
vm.numbers.splice(index, 1);
}
}
});
</script>
</head>
<body ng-controller="defaultCtrl as vm">
<div ng-repeat="num in vm.numbers">
<span>Number : {{num}}</span>
</div>
<div>
<input type="number" ng-model="vm.newNumber">
<button ng-click="vm.add(vm.newNumber)">Add</button>
<button ng-click="vm.remove(vm.newNumber)">Remove</button>
</div>
</body>
</html>

Angular error : Expected array but received: 0

I'm getting this error when I open up a model partial:
<form action="" novalidate name="newGroupForm">
<div class="modal-body">
<div class="row">
<!-- SELECT THE NUMBER OF GROUPS YOU WANT TO CREATE -->
<label>Select number of groups</label>
<a class="btn" ng-click="newGroupCount = newGroupCount + 1" ng-disabled="newGroupCount == 10" ><i class="fa fa-plus-circle"></i></a>
<input class="groupCounter input-sm" ng-model="newGroupCount" type="number" min="1" max="10" disabled>
<a class="btn" ng-click="newGroupCount = newGroupCount - 1" ng-disabled="newGroupCount == 1"><i class="fa fa-minus-circle"></i></a>
</div>
<br>
<table class="table table-striped">
<thead>
<tr>
<th>#</th>
<th>Group Name</th>
<th>Group Description (optional)</th>
</tr>
</thead>
<tbody>
<tr ng-repeat="i in getNumber(newGroupCount) track by $index">
<td>{{$index+1}}</td>
<td>
<input class= input-sm type="text" required="true" autofocus="true" placeholder="Group name" ng-model="groupData.title[$index]">
</td>
<td>
<input class="form-control input-sm" type="textarea" ng-model="groupData.desc[$index]" placeholder="Group Description">
</td>
</tr>
</tbody>
</table>
</div>
<div class="modal-footer">
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
<button class="btn btn-primary" type="submit" ng-click="submit()" ng-disabled="newGroupForm.$invalid">Create</button>
</div>
</form>
The modal controller looks like this:
spApp.controller('newGroupCtrl',
function newGroupCtrl($scope, $uibModalInstance, GroupService){
$scope.groupData = {
title: [],
desc: []
}
$scope.newGroupCount = 1;
$scope.getNumber = function(num) {
//console.log(num);
return new Array(num);
}
$scope.submit = function(){
$uibModalInstance.close($scope.groupData);
}
$scope.cancel = function (){
$uibModalInstance.dismiss('Cancelled group creation');
};
}
);
Every question I've seen refers to the use of filterbut I'm not using filter. The error repeats whenever I hit the increment button:
<a class="btn" ng-click="newGroupCount = newGroupCount + 1" ng-disabled="newGroupCount == 10" ><i class="fa fa-plus-circle"></i></a>
$scope.getNumber calls new Array(num), which will return an array of undefined values directly proportional to the value of newGroupCount.
For example:
new Array(5) // => [undefined, undefined, undefined, undefined, undefined]
Browsers don't handle that well, since it appears to be an empty array.
You're using ng-repeat in a way that it wasn't quite meant to be used. If I were you, I'd refactor to look something like this:
$scope.groups = [];
$scope.addGroup = function() {
// implement this, and use it in your button that increments the groups
$scope.groups.push(/* whatever */);
}
$scope.removeGroup = function() {
// implement this to remove a group
$scope.groups.splice(/* whatever */);
}
Then in your HTML:
<tr ng-repeat="group in groups">
<!-- display group info -->
</tr>
It may make your life easier here to work with angular (use it how it was intended) instead of fighting against how ng-repeat is meant to work.
The data is generally meant to be in the form of a collection (i.e. [{},{},{}]). Formatting it as such will make it easier for you. See the docs for ng-repeat.

Categories

Resources