$scope seems to be missing binding properties for a text box typeahead and a select list.
Here is a fiddle showing the behavior I want: http://jsfiddle.net/langtonr/h4MKa/1/
You can add and remove salesmen as desired. The $scope is binding correctly to $scope.salesmanEntry (the salesman selected in the typeahead) and $scope.selectedSalesmen (salesmen that have been selected in the list box).
This same code I have copied and pasted into my project and it does not work. I am using all the same library references. It seems scope just doesn't work for these 2 variables. $scope.salesmanEntry and $scope.selectedSalesmen are always undefined. My full controller code is below. What could possibly be interfering with my $scope?
I'm outputting the following on my view:
<div>{{salesmanEntry.id}} {{salesmanEntry.displayName}}</div>
My view always shows these correctly, so when I have typed a salesman, it shows up, yet when I get to my controller code, $scope.salesmanEntry is always undefined. It exhibits the same behavior with $scope.selectedSalesmen. I can output to the screen by using an ng-repeat on selectedSalesmen, yet in my controller $scope.selectedSalesmen is always undefined.
manageProductsModule.controller('productDetailCtrl', [
'productSvc', 'equipmentOptionSvc', 'salesmanSvc', '$scope', function (productSvc, equipmentOptionSvc, salesmanSvc, $scope) {
$scope.loadingProduct = false;
$scope.product = {
secureUsers: [],
options: [],
priceHistory: []
};
$scope.addedSalesmen = [];
$scope.allSalesmen = [
{ id: 1, displayName: "Smith, John" },
{ id: 2, displayName: "Ramsey, Gordon" },
{ id: 3, displayName: "White, Betty" }];
$scope.addSalesman = function (salesman) {
if (!salesman.id) return;
var result = $.grep($scope.addedSalesmen, function (e) { return e.id == salesman.id; });
if (result.length === 0) {
$scope.addedSalesmen.push(salesman);
}
$scope.salesmanEntry = { id: -1, displayName: "" };
};
$scope.removeSalesmen = function () {
$scope.selectedSalesmen.forEach(function (salesman) {
var index = $scope.addedSalesmen.indexOf(salesman);
$scope.addedSalesmen.splice(index, 1);
});
$scope.selectedSalesmen = [];
}
}
]);
template:
<div ng-app="manageProductsModule">
<div class="container" ng-controller="productDetailCtrl">
<h3>Model Details</h3>
<form class="form-horizontal" role="form" ng-if="!loadingProduct">
<div class="form-group">
<label for="addSalesmenInput" class="control-label col-md-2">Add</label>
<div class="col-md-4">
<div class="input-group">
<input name="addSalesmenInput"
class="form-control"
ng-model="salesmanEntry"
typeahead="salesman as salesman.displayName for salesman in allSalesmen | filter:$viewValue" />
<div class="input-group-btn">
<button class="btn" ng-click="addSalesman(salesmanEntry)" ng-disabled="!salesmanEntry">
<i class="glyphicon glyphicon-plus"></i>
</button>
</div>
</div>
</div>
</div>
<div class="form-group">
<label for="salesmenInput" class="control-label col-md-2">Salesmen With Access</label>
<div class="col-md-4">
<select name="salesmenInput" multiple="multiple" size="8" class="form-control" ng-model="selectedSalesmen" ng-options="salesman as salesman.displayName for salesman in addedSalesmen"></select>
<button class="btn" ng-click="removeSalesmen()" ng-disabled="!selectedSalesmen || selectedSalesmen.length === 0">
<i class="glyphicon glyphicon-minus"></i>
</button>
</div>
</div>
</form>
</div>
</div>
So here is what I had to do to get this to work. I am definitely not happy with what seems like a very hacky solution and would like to know why this is necessary. I had to go to $scope.$$childHead to access salesmanEntry and selectedSalesmen as they are apparently being stored on the wrong scope.
$scope.addSalesman = function (salesman) {
if (!salesman.id) return;
var result = $.grep($scope.addedSalesmen, function (e) { return e.id == salesman.id; });
if (result.length === 0) {
$scope.addedSalesmen.push(salesman);
}
$scope.$$childHead.salesmanEntry = { id: -1, displayName: "" };
};
$scope.removeSalesmen = function () {
$scope.$$childHead.selectedSalesmen.forEach(function (salesman) {
var index = $scope.addedSalesmen.indexOf(salesman);
$scope.addedSalesmen.splice(index, 1);
});
$scope.$$childHead.selectedSalesmen = [];
}
Related
I am simply trying to create some checkboxes and inputs that get the data from database and saving it back to database after edit. But I am getting the following error:
Uncaught TypeError: Unable to process binding "if: function(){return $root.editAlarmValues }"
Message: Unable to process binding "enable: function(){return $root.editAlarmValues().setAlarmValues() }"
Message: $root.editAlarmValues(...).setAlarmValues is not a function
I am not sure what I am doing wrong. I checked in the console and the values get mapped correctly to the array but they don't seem to bind to view. Any help will be highly appreciated!
Here is the code:
<!--ko if: $root.editAlarmValues -->
<div class="row">
<div class="col-md-6">
<input type="checkbox" data-bind="iCheck: $root.editAlarmValues().setAlarmValues" class="large-check"/>
</div>
</div>
<div class="row">
<div class="col-md-5 form-inline">
<input type="checkbox" data-bind="iCheck: $root.editAlarmValues().setOutputCurrentPPLowValue, enable: $root.editAlarmValues().setAlarmValues()" class="large-check"/>
<input type="text" id="OutputCurrentPPLowValue" data-bind="value: $root.editAlarmValues().outputCurrentPPLowValue, enable: $root.editAlarmValues().setOutputCurrentPPLowValue()" class="form-control" maxlength="30" />
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<button type="button" class="btn btn-primary btn-flat" data-bind="event: {click: $root.editSave}">Save</button>
</div>
</div>
</div>
<!-- /ko -->
and script:
var AlarmsViewModel = function (wellID) {
function EditAlarms(setAlarmValues, setOutputCurrentPPLowValue, outputCurrentPPLowValue) {
var self = this;
self.setAlarmValues = ko.observable(setAlarmValues);
self.setOutputCurrentPPLowValue = ko.observable(setOutputCurrentPPLowValue);
self.outputCurrentPPLowValue = ko.observable(outputCurrentPPLowValue);
}
var self = this;
self.wellID = ko.observable(wellID);
self.editAlarmValues = ko.observableArray();
self.init = function () {
self.editAlarmInit();
};
self.editAlarmInit = function () {
APIHelper.getData("/api/alarmapi/EditAlarms?wellID=" + self.wellID(), self.editAlarmsCallback, "");
};
self.editAlarmsCallback = function (data) {
//Map results
var temp = $.map(data.result, function (item) {
return new EditAlarms(item.setAlarmValues, item.setOutputCurrentPPLowValue, item.outputCurrentPPLowValue);
});
self.editAlarmValues(temp);
};
self.editSave = function () {
var jsonData = ko.toJSON(self.editAlarmValues);
APIHelper.postData("/api/alarmapi/EditAlarmsPost", jsonData);
};
self.init();
};
var wellID = #ViewBag.WellID;
ko.bindingHandlers.iCheck = {
init: function (el, valueAccessor) {
var observable = valueAccessor();
$(el).on("ifChanged", function () {
observable(this.checked);
});
},
update: function (el, valueAccessor) {
var val = ko.utils.unwrapObservable(valueAccessor());
if (val) {
$(el).iCheck('check');
} else {
$(el).iCheck('uncheck');
}
}
};
var vm = new AlarmsViewModel(wellID);
ko.applyBindings(vm);
You don't want a <!-- ko if: editAlarmValues -->, you want a <!-- ko foreach: editAlarmValues -->. The foreach will not run when the target array is empty, so it fundamentally fulfills the same function.
Inside the foreach, the binding context is the EditAlarms object in question, so you should refer to its properties directly (iCheck: setOutputCurrentPPLowValue instead of iCheck: $root.editAlarmValues().setOutputCurrentPPLowValue).
Also think about your naming. EditAlarms is not a good name for a single object. The prefix set... should refer to a method that sets something. In this case it's just an observable property. setAlarmValues should be called alarmValues, and because it's not an array, it probably should actually be called alarmValue. And so on.
<!-- ko foreach: editAlarmValues -->
<div class="row">
<div class="col-md-6">
<input type="checkbox" data-bind="iCheck: setAlarmValues" class="large-check">
</div>
</div>
<div class="row">
<div class="col-md-5 form-inline">
<input type="checkbox" class="large-check" data-bind="
iCheck: setOutputCurrentPPLowValue,
enable: setAlarmValues
">
<input type="text" id="OutputCurrentPPLowValue" class="form-control" maxlength="30" data-bind="
value: outputCurrentPPLowValue,
enable: setOutputCurrentPPLowValue
">
</div>
</div>
<div class="row">
<div class="col-md-12">
<div class="pull-right">
<button type="button" class="btn btn-primary btn-flat" data-bind="click: $root.editSave">Save</button>
</div>
</div>
</div>
<!-- /ko -->
Revised JS code (move the binding handler definitions to the top, don't nest viewmodels)
ko.bindingHandlers.iCheck = {
init: function (el, valueAccessor) {
var observable = valueAccessor();
$(el).on("ifChanged", function () {
observable(this.checked);
});
},
update: function (el, valueAccessor) {
var val = ko.unwap(valueAccessor());
$(el).iCheck(val ? 'check' : 'uncheck');
}
};
function EditAlarms(setAlarmValues, setOutputCurrentPPLowValue, outputCurrentPPLowValue) {
this.setAlarmValues = ko.observable(setAlarmValues);
this.setOutputCurrentPPLowValue = ko.observable(setOutputCurrentPPLowValue);
this.outputCurrentPPLowValue = ko.observable(outputCurrentPPLowValue);
}
function AlarmsViewModel(wellID) {
var self = this;
self.wellID = ko.observable(wellID);
self.editAlarmValues = ko.observableArray();
self.editAlarmInit = function () {
APIHelper.getData("/api/alarmapi/EditAlarms?wellID=" + self.wellID(), function (data) {
var alarms = data.result.map(item => new EditAlarms(
item.setAlarmValues,
item.setOutputCurrentPPLowValue,
item.outputCurrentPPLowValue
));
self.editAlarmValues(alarms);
});
};
self.editSave = function () {
APIHelper.postData("/api/alarmapi/EditAlarmsPost", ko.toJSON(self.editAlarmValues));
};
self.editAlarmInit();
}
var vm = new AlarmsViewModel(#ViewBag.WellID);
ko.applyBindings(vm);
I am trying to build filter system with Vue.
Updated
Filters working, but all the functions computed are separeted functions. So How can I make those in one function and use it.
export default {
data() {
return {
estates: [],
search: '',
regions:['関西','関東','京橋'],
checkedRegions:[]
}
},
created(){
axios.get('/ajax').then((response) => {
this.estates = response.data;
});
},
computed: {
one: function() {
var result = this.estates.filter((estate) =>
estate.building_name.match(this.search)
);
if(this.checkedRegions.length && this.checkedRooms.length) {
return result.filter(estate => this.checkedRegions.includes(estate.region) && this.checkedRooms.includes(estate.rooms))
}
return result;
}
}
}
<div class="container-fluid">
<div class="row">
<div class="col-md-9">
<input type="text" v-model="search" name="" placeholder="search estate" value="">
<div v-for="estate in filteredestate" class="card-body">
<h2>{{estate.building_name}}</h2>
<p>{{estate.address}}</p>
</div>
</div>
</div>
</div>
filteredestate filteredRegions and filteredRooms make one function. for example how to return those function with && ? And use it in this div.
<div v-for="estate in oneFunction" class="card-body">
Set your filter search result first as variable and you can check filter by or(||) expression !
I modified this inside arrow function by setting to that variable and on the last line return result as default
one: function() {
var that = this;
var result = this.estates.filter((estate) =>
estate.building_name == that.search;
);
if(this.checkedRegions.length || this.checkedRooms.length) {
return result.filter(estate => that.checkedRegions.includes(estate.region) || that.checkedRooms.includes(estate.rooms))
}
// when region and room length is 0
return result;
}
}
rooms and regions are arrays. So you need to iterate through these arrays in order to render the checkboxes.
instead of this:
<input type="checkbox" v-model="checkedLocations" v-bind:value="regions">関東 <input/>
<input type="checkbox" v-model="checkedLocations" v-bind:value="regions">関西 <input/>
<input type="checkbox" v-model="checkedLocations" v-bind:value="regions">北海道<input/>
should be like this:
<template v-for="region in regions">
<input type="checkbox" v-model="checkedLocations" v-bind:value="region.id">region.region<input/>
</template>
similar should be done with rooms.
Also, in js part, you have checkedRegions while in template you have checkedLocations. I guess this should be too checkedRegions.
I have an array of objects with user and interests. I would like to filter the list based on interests. An user can have multiple interests and when selected interests check boxes the list should filter out based on those interests. I've used a basic filter on ng-repeat but it's not working.
If I select "sports" then "John" and "Rosaline" should be shown.
If I select "movies" and "reading" then all 3 users should be shown.
Below is my code.
Plunker: https://plnkr.co/edit/A0ojO3MH8rDhFJVXlEAs?p=preview
View:
<div ng-app="myApp" ng-controller="myController" style="padding:20px">
<input type="checkbox" ng-model="selection.reading" ng-true-value="'reading'" ng-false-value="''">reading
<input type="checkbox" ng-model="selection.sports" ng-true-value="'sports'" ng-false-value="''">sports
<input type="checkbox" ng-model="selection.movies" ng-true-value="'movies'" ng-false-value="''">movies
<br><br>
Selected Values: {{selectedValues}}
<hr>
<div ng-repeat="d in data | filter: selectedValues">
<h4>
Name: {{d.user}}
</h4>
<h4>
Interests: {{d.interests}}
</h4>
<hr>
</div>
Controller:
$scope.selection = {};
$scope.data = [
{
user: "John",
interests: ["reading","sports","movies"]
},
{
user: "David",
interests: ["movies","reading"]
},
{
user: "Rosaline",
interests: ["sports","movies"]
}
];
$scope.$watchCollection('selection', function () {
$scope.selectedValues = [];
angular.forEach($scope.selection, function (value, key) {
if (value) {
$scope.selectedValues.push(value);
}
});
});
Here is one of many ways to filter objects in such examples.
I suggest getting rid of using $watch in this situation and implement checkboxes functionality via simple ngChange directive. Example below.
<input type="checkbox" ng-model="selection.reading" ng-change="selectInterest('reading')">reading
<input type="checkbox" ng-model="selection.sports" ng-change="selectInterest('sports')">sports
<input type="checkbox" ng-model="selection.movies" ng-change="selectInterest('movies')">movies
$scope.selectInterest = function(interest){
$scope.selection[interest] = !!$scope.selection[interest];
};
For filtering data I recommend using $filter or (just to simplify example) implement this filter like a controller function.
$scope.filterUsers = function(user){
return Object.keys($scope.selection).some(function (item) {
return user.interests.indexOf(item) >= 0 && $scope.selection[item];
});
};
You can look at and play with the whole code of my example at the link above.
Modify your html a bit in ng-repeat
<div ng-repeat="d in data" ng-hide="selectedValues.length > 0 && !filteredData(d.interests)">
<h4>
Name: {{d.user}}
</h4>
<h4>
Interests: {{d.interests}}
</h4>
<hr>
</div>
And add this function in script.js
$scope.filteredData = function (data) {
var found = false;
for (var i = 0; i < $scope.selectedValues.length; i++) {
if (data.indexOf($scope.selectedValues[i]) !== -1) { found = true; break; }
}
return found;
};
Live example :
https://plnkr.co/edit/y8tqNiIU6p3x8d9zcdzL?p=preview
This will work ! Thanks
Your can keep your filters in some array e.g. filterInterests and then your HTML could looks like:
<div ng-repeat="d in data">
<div ng-show="hasInterest(d.interests, filterInterests)">
// Your code here
</div>
</div>
And your function will looks like:
function hasInterest(data, interests){
var result = false;
// Iterate over interests from filter and check them all in provided data
for(var=i, i<interests.length, i++){
if(data.indexOf(i) != -1) result = true
else result = false;
}
return result;
}
I'm new to AngularJS, and, for a project, I need to make an interactive search engine.
So I did this for now :
article/views/articles.html
<form class="form-inline">
<div class="form-group">
<label for="filter-text">Text </label>
<input type="search" class="form-control" id="filter-text" placeholder="Search for text" ng-change="applyFilter();" ng-model="filters.text">
</div>
<div class="form-group">
<label for="filter-date-start">Entre </label>
<input type="date" class="form-control" id="filter-date-start" placeholder="Entre" ng-change="applyFilter();" ng-model="filters.date_start">
</div>
<div class="form-group">
<label for="filter-date-end">Et </label>
<input type="date" class="form-control" id="filter-date-end" placeholder="Et" ng-change="applyFilter();" ng-model="filters.date_end">
</div>
</form>
<div class="hidden-sm hidden-xs col-md-6">
<div
ng-repeat="article in articles"
class="article"
ng-include="'article/views/article.html'" >
</div>
</div>
article/views/article.html
<div ng-hide="article.isHidden">
<!-- My Article DOM -->
</div>
article/article.js
angular
.factory('articleRetriever', function ($http, $q){
this.getLast = function( id ){
var url = 'http://localhost:8080/articles/';
if (id) url += id;
return $http.get(url ,{'Access-Control-Allow-Origin': 'localhost:*'})
.then(function(response) {
var articles = [];
for (var idx in response.data.data) {
var article = response.data.data[idx];
article.isHidden = false;
articles.push(article);
}
return articles;
});
};
return this;
})
.controller('ArticlesCtrl', ['$scope', 'articleRetriever', function($scope, articleRetriever) {
$scope.articles = [];
$scope.filters = { tag: null, date_start : null, date_end : null, text : null };
articleRetriever.getLast()
.then(function(arrItems){
$scope.articlesLoaded = arrItems;
$scope.articles = $scope.articles.concat(arrItems);
});
$scope.applyFilter = function () {
var contains = $scope.filters.text.split(' ');
for (var idx in $scope.articles) {;
$scope.articles[idx].isHidden = true;
if (contains.length > 0) {
for( var jdx in contains) {
if ($scope.articles[idx].body.toUpperCase().indexOf( contains[jdx] ) > -1)
$scope.articles[idx].isHidden = false;
if ($scope.articles[idx].title.toUpperCase().indexOf( contains[jdx] ) > -1)
$scope.articles[idx].isHidden = false;
}
}
}
};
});
But when I fill the input with some text, the modification on $scope.articles didn't hide the article's div in article/views/article.html.
Can someone explain why and could give me a solution ?
Thanks :-)
My bad, the search form wasn't inside the div with the ng-controller="ArticleCtrl" directive, I moved it inside and all works perfectly now.
I have a form long enough that I want to enable users to save their progress and finish it later. It has multiple Knockout view models (all grids) as well as good old-fashioned inputs.
Here's an example of the code powering one of the grids (they're all basically the same):
/**
* Goals
* #param {string} goal_secondary value of input[name=goal_secondary]
* #param {string} relation_goal_secondary value of textarea[name=relation_goal_secondary]
*/
function Goal(goal_secondary, relation_goal_secondary) {
var self = this;
self.goal_secondary = goal_secondary;
self.relation_goal_secondary = relation_goal_secondary;
}
function GoalsVM() {
var self = this;
// Apparently moves trigger afterAdd, but I don't want it to, so let's pretend it doesn't.
move = false;
self.goals = ko.observableArray([
new Goal('', '')
]);
self.addGoal = function() {
self.goals.push(new Goal('', ''));
}
self.removeGoal = function(goal) {
self.goals.remove(goal);
}
self.afterAdd = function(el) {
// Don't do this stuff on move!
if (el.nodeType === 1 && !move) {
$(el).hide().slideDown();
}
// Reset move in case the user's next action is to add a new goal.
move = false;
}
self.beforeMove = function(el) {
// Tell afterAdd this is a move, not really an add.
move = true;
}
self.beforeRemove = function(el) {
if (el.nodeType === 1) {
$(el).slideUp(function() {
$(el).remove();
})
}
}
}
Here's the associated markup:
<ul class="grid list-unstyled"
data-bind="sortable: {
data: goals,
afterAdd: afterAdd,
beforeRemove: beforeRemove,
beforeMove: beforeMove
}">
<li class="row">
<div class="col-xs-1"><span class="move">≡</span></div>
<div class="col-xs-10">
<input class="form-control"
data-bind="value: goal_secondary,
attr: { name: 'goal_secondary_' + $index() }">
<textarea rows="3" class="form-control"
data-bind="value: relation_goal_secondary,
attr: { name: 'relation_goal_secondary_' + $index() }"></textarea>
</div>
<div class="col-xs-1">
<button class="btn text-danger btn-xs remove" data-bind="click: removeGoal">
×
</button>
</div>
</li>
</ul>
I was trying to follow the Loading and Saving JSON data page in the docs, but ko.toJSON(GoalsVM) returns undefined.
I also tried using var Goal = function(goal_secondary, relation_goal_secondary) {…} and var GoalsVM = function() {…}, but I still got undefined.