Angular: further dynamic filtering issue - angularjs

I'm trying to get these dynamic filters working and its almost there I think. Looks like the model isn't being passed back to the watch function. I would've expected it to work with the first set of filters at least but it doesn't.
What I'm trying to achieve is that when the user selects an option from the select list and sets a value in the input box the data is filtered. The user can optionally add another set of filters by clicking the "add fields" button. If there user completes the second set of filters then the data is filtered further.
This is what I've got so far. If there's only one filter showing then shouldn't it just work?
This is the code that creates the user defined filters.
<div data-ng-repeat="filter in filters">
<select ng-model="filter.id">
<option value="age">Age</option>
<option value="customerId">CustomerID</option>
<option value="productId">ProductID</option>
<option value="name">Name</option>
</select>
<input type="text" ng-model="data.search[filter.id]">
<button class="remove" ng-show="$last" ng-click="removeFilter()">-</button>
Add fields
I think I'm almost there, got a better understanding of hierarchical scope. I've referred to a few tutorial and examples but I'm not quite there yet. I think my issue with this is that I'm not communicating the models properly. Still a little bit lost with this. Any further tips/suggestions would be great. I'm wondering if I should move some of he code from the controller in my directive into the resultsCtrl controller. Really not sure.
This fiddle gave me the idea to use filter.id within my template ng-repeat
This plnkr was helpful.
I'm getting somewhere now. This plnkr shows it working, the last thing I want to do with it is when you remove a filter it automatically updates the search object to remove the relevant filter.
Any suggestions on how to do this?

You are breaking the golden rule of "always have a dot in ng-model".
Why? because objects have inheritance and primitives do not.
ng-model="filterType"
is being defined inside a child scope created by ng-repeat. Because it is a primitive the parent scope in controller can not see it. However if it was a property of an object that was available at parent level, the reference would be shared between parent and child.
Once you fix that bug you will run into a bug where scope.search is not an object either so your $watch function will throw an exception also. In main controller can set:
$scope.search ={};
Do some reading up on how hierarchical scopes work in angular. There is plenty to be found in web searches and angular docs

I eventually got this solved :)
directive:
angular.module('dynamicFiltersDirective', [])
.directive('dynamicFilters', function() {
return {
restrict: 'AE',
scope: {
filtersArray: '=',
searchObject: '='
},
link: function(scope) {
scope.addFilter = function() {
var newItemNo = scope.filtersArray.length+1;
scope.filtersArray.push({'id':'filter'+newItemNo});
};
scope.removeFilter = function(option) {
var lastItem = scope.filtersArray.length-1;
scope.filtersArray.splice(lastItem);
delete scope.searchObject[option];
};
scope.updateFilters = function (model) {
scope.searchObject[model];
}
},
templateUrl: 'filtertemplate.html'
}
});
controller:
angular.module('app', ['dynamicFiltersDirective']).controller('resultsCtrl', function($scope){
$scope.filters = [{id: 'filter1'}];
$scope.search = {};
$scope.persons = [{
name: 'Jim',
age: 21,
customerId: 1,
productId: 4
},{
name: 'Frank',
age: 20,
customerId: 2,
productId: 4
},{
name: 'Bob',
age: 20,
customerId: 3,
productId: 5
},{
name: 'Bill',
age: 20,
customerId: 3,
productId: 5
}];
});
template:
<div data-ng-repeat="filter in filtersArray">
<select ng-model="filter.id">
<option value="age">Age</option>
<option value="customerId">CustomerID</option>
<option value="productId">ProductID</option>
<option value="name">Name</option>
</select>
<input type="text" ng-model="searchObject[filter.id]" ng-change="updateFilters(searchObject[filter.id])">
<button class="remove" ng-show="$last" ng-click="removeFilter(filter.id)">-</button>
</div>
<button class="addfields" ng-click="addFilter()">Add fields</button>
<div id="filtersDisplay">
{{ filters }}
</div>
index.html
<div ng-controller="resultsCtrl">
<dynamic-filters filters-array="filters" search-object="search"> </dynamic-filters>
<div ng-repeat="person in persons | filter: search">
{{person.name}}
</div>
Here's my example plnkr

Related

$watch not displaying change with two ui-views

I have $watch function where it watches whenever a country is selected, but it's not saving the changed data to the scope. I had one ui-view, which worked fine, but then I changed the structure and now have two ui-views under same page, both uses the same controller. Reason for the change was for was to have a clean html page, I could always go back to one ui-view, but it's messy. This is where it started having problems.
here is app.js
$stateProvider
.state('homepage',{
templateUrl: '/static/partial/home/homeLayout.html',
controller: 'UploadFileCtrl',
})
.state('homepage.show',{
url: '/',
views: {
querySetup: {
templateUrl: '/static/partial/home/querySetup.html',
},
queryResults: {
templateUrl: '/static/partial/home/queryResults.html',
},
}
})
layout.html - ui-views:
<div class="col-sm-12 panel-container">
<div class="col-sm-6" style="height: 100%;">
<div ui-view="querySetup" ></div>
</div>
<div class="col-sm-6" style="height: 100%;">
<div ui-view="queryResults"></div>
</div>
</div>
controller watch:
$scope.currentCountries = [
{
name: 'United States',
code: 'US'
},
{
name: 'United Kingdom',
code: 'GB'
},
{
name: 'Germany',
code: 'DE'
},
{
name: 'France',
code: 'FR'
},
{
name: 'China',
code: 'CN'
},
{
name: 'Japan',
code: 'JP'
}
];
$scope.$watch('pickedCountry', function(){
if($scope.pickedCountry != null){
$scope.countryCode = $scope.pickedCountry.code;
}
});
here is querySetup view:
<select name="brand" ng-model = "pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>
I moved the controller out of the state homepage, and set for each view under the state homepage.show. This worked fine, but an extra operation is not working. When selecting a brand, it's supposed to run a calculation and set it in the queryResults view. The results is set to a scope which have to be displayed to queryResults.
here is queryResults:
<div class="panel panel-info">
<div class="panel-heading">
<h3 class="panel-title">OSM RESULTS</h3>
</div>
<div class="panel-body">
<textarea rows="50" cols="100" class="xmlOutput">
{{ queryResults }}
</textarea>
</div>
</div>
Please help! Not familiar with Angular $watch, and I think the problem comes when there is two ui-views. Thanks in advance for your help.
UPDATE
Found the solution to my problem, so I need to add $parent to my model and pass the changed data as a param to my $watch function:
html update:
<select name="brand" ng-model = "$parent.pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>
controller update
$scope.$watch('pickedCountry', function(data){
if(data != null){
$scope.countryCode = data.code;
}
});
This solved the issue, but still don't quite understand why I needed to add the parent. Can someone explain why?
You have your controller set up to live in homepage, but your templates live in homepage.show which is a child state of homepage. In order to access the variables from that controller you can use $parent (as you've discovered) to access the parent state's scope.
I would recommend restructuring your state definition though so you don't have to deal with that.
.state( 'homepage', {
url: '/homepage',
views: {
'':{
templateUrl:'/static/partial/home/homeLayout.html',
controller: 'UploadFileCtrl',
},
'querySetup#homepage': {
templateUrl:'/static/partial/home/querySetup.html'
},
'queryResults#homepage':{
templateUrl:'/static/partial/home/queryResults.html'
}
}
})
I believe it is because child states operate the same as prototypical inheritance (scope: true) on directives.
You might consider defining in your controller:
$scope.input = {};
$scope.$watch('input.pickedCountry', function(data){
if(data != null){
$scope.countryCode = data.code;
}
});
and in the querySetup template:
<select name="brand" ng-model = "input.pickedCountry"
data-ng-options="country as country.name for country in currentCountries">
<option value="">Select Brand</option>
</select>

Angular - Data Bind Issue

So the main functionality I want is here, which is selecting an option from the drop-down menu and having it populate my input field.
JSFiddle:
<div ng-app="app" ng-controller="MainCtrl">
Two things I want to fix:
When typing into the input ("Email Subject") field I don't wan't it to change the drop-down menu option.
I want the input field to initialize with it's placeholder value of ("Email Subject") instead of initializing with "Select a Canned Response"
I'm assuming this means making the input field have a one-way data bind, but I'm not sure how to do this, any help is appreciated.
<div ng-app="app" ng-controller="MainCtrl">
<input ng-model="CannedResponse" placeholder="Email Subject"><!--change this-->
<div class="item item-input item-select" >
<div class="input-label"> </div>
<select ng-model="newSelectedITem" ng-options="CannedResponse as CannedResponse.label for CannedResponse in CannedResponses"
ng-change="yourFunction()"> <!--change this-->
<option value="{{item.value}}"> </option>
</select>
</div>
</div>
js code
angular.module('app', [])
.controller('MainCtrl', function($scope) {
$scope.CannedResponses = [{
label: 'Selet a Canned Response',
value: 0
}, {
label: 'Hi there whats up',
value: 1
}, {
label: 'Here is another response',
value: 2
}, {
label: 'Why not select this one?',
value: 3
}];
$scope.newSelectedITem = $scope.CannedResponses[0] //ADD THIS (X1)
$scope.CannedResponse='';//add this
$scope.yourFunction = function() {//add this function
$scope.CannedResponse = $scope.newSelectedITem.label;
};
});
see where I wrote change this. There where you have to change your code.

Bind textbox to selectList Angular

Is there any way to bind textbox to select list without $Watch .
the Plunker
i don't want to let user type everything and bind to selectlist .
Thanks
So if you need to make a select and to display the result in your input without $scope.$watch just use the attribut disabled on the input. Try this :
<input type="text" ng-model="text.name" disabled/>
<select ng-model="text" ng-options="option.name for option in options">
</select>
In your controller :
app.controller('MainCtrl', function($scope) {
$scope.options = [{
name: 'a',
value: 'something-cool-value'
}, {
name: 'b',
value: 'something-else-value'
}];
You can try in your plunker this is working. Hope this helped.

Set angularjs input directive name as a variable

I'm trying to set an input like
<form name="myForm">
<input name="{{ name }}"/>
</form>
It works in the dom. I see
<input name="whatever_name_is_set_to"/>
However in my ngForm I have
$scope.myForm: {
{{ name }}: { }
}
Doh'
Why am I doing this? I'm trying to create a directive so that I can build my forms programmatically. Then I can do something like
<div my-field
name="credits"
field="course.credits"
field-options="courseOptions.credits"
title="Credits"
></div>
Plunker
Updated 2017-03-23:
For AngularJS >= 1.3 interpolation is now supported in input names.
Original answer from 2014-06-05 (correct for AngularJS <= 1.2):
I just answered a similar question yesterday about dynamically named form elements. In short, no, you can't use interpolation to dynamically name your form fields - the interpolation string will end up in the field name as you have seen.
In your case you're probably going to need to look into dynamically compiling the input HTML inside your myField directive.
Here is a simplified example using $compile to dynamically generate form elements: http://jsfiddle.net/Sly_cardinal/XKYJ3/
HTML:
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<div my-field
name="courseName"
field="course.courseName"
title="Course Name"></div>
<div my-field
name="credits"
field="course.credits"
title="Credits"></div>
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits.$dirty: {{myForm.credits.$dirty}}</pre>
</form>
</div>
JavaScript:
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: 100,
courseName: 'Programming 201'
};
}])
.directive('myField', ['$compile', '$parse', function($compile, $parse){
// In a real project you'd probably want to use '$templateCache' instead
// of having strings in your code.
var tmpl = $compile('<label>{{title}}</label>');
return {
scope: true,
link: function(scope, element, attr){
scope.title = attr.title;
var newEl = angular.element('<input type="text"/>');
newEl.attr('ng-model', attr.field);
newEl.attr('name', attr.name);
tmpl(scope, function(fieldEl, scope){
$compile(newEl[0].outerHTML)(scope, function(el, scope){
fieldEl.append(el);
element.append(fieldEl);
});
});
}
}
}]);
A note on this example:
This is a very specific situation - generating dynamic form elements - that requires the use of $compile. This is not the "go to" solution when working with Angular inputs and forms - Angular will handle all the common situations with directives, data-binding and everything else the framework provides. Plus, as Marc Kline's comment shows, it looks like at some point Angular will handle dynamic form management itself at some point in the future.
If you were to continue down the path using $compile to generate these form elements then you'd probably want to use the $templateCache to manage your templates so you're not trying to manage template strings inside your directive.
Old question, but in case someone is looking for a way to do what question asked, you can create a directive that will dynamically create the name of the element after $compile'ing it.
An updated version of the answer that #Sly_cardinal posted is here: http://jsfiddle.net/XKYJ3/1/
HTML
<div ng-app="myApp">
<form name="myForm" ng-controller="myController">
<label for="{{ course.courseName.name }}" ng-bind="course.courseName.title"></label>
<input id="{{ course.courseName.name }}" dynamic-input-name="course.courseName.name" ng-model="course.courseName.value" type="text" required />
<br />
<label for="{{ course.credits.name }}" ng-bind="course.credits.title"></label>
<input id="{{ course.credits.name }}" dynamic-input-name="course.credits.name" ng-model="course.credits.value" type="number" required />
<!-- Show that the values are bound. -->
<pre>course: {{course | json:' '}}</pre>
<!-- Show that the field is being registered with the ngFormController. -->
<pre>myForm.credits_field.$dirty: {{ myForm.credits_field.$dirty }}</pre>
</form>
</div>
Javascript
angular.module('myApp', [])
.controller('myController', ['$scope', function($scope){
$scope.course = {
credits: {
title: 'Credits',
value: 100,
name: 'credits_field'
},
courseName: {
title: 'Course name',
value: 'Programming 201',
name: 'course_name_field'
}
};
}])
.directive('dynamicInputName', ['$compile', '$parse', function($compile, $parse){
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-input-name'))(scope);
elem.removeAttr('dynamic-input-name');
elem.attr('name', name);
$compile(elem)(scope);
}
};
}]);

Using objects/arrays as checkbox values and sharing across controllers

I've got a two-part form, where one page lets a user select via checkbox from several lists. Having made those selections, the user can see a summary of selections, on another page. The user can then return to the checkbox list and check/uncheck as needed.
I can get it to work so the user's selections persist, but then I can't get the output to show up properly on the main page (the summary list). Or, if I get the output to show up nicely with item.name, then I can't get the selections to persist when the user returns to the secondary page's list of checkboxes. Sharing across controllers is NOT the problem, really. The problem here is getting the checkboxes to retain their checked-state when the user returns to the checkbox list. When I output {{checkedCity}}, for instance, I can see everything's in there. I just can't get the checkboxes to reflect this.
This seems to revolve around how the data is entered in the factory: if I use value="item.name" in the checkbox input, then the name string is all that's added to the factory. But I need the id, as well, since that's what I'll be actually sending the api. So if I change to value="item", then I can get the output to look nice, and I get the id -- but then I don't get the persistence if the user revisits the page.
My instinct (which has gotten me nowhere, sadly) is the factory can't see that its contained objects are the same as what's being spit out in the checkbox ng-repeat. I've tried ng-click, ng-checked, and now I'm trying using the webStorage plugin, instead. I've also attempted to cycle through the factory and compare it to the checkbox list.
Here the plunk: http://plnkr.co/edit/akvFJrPP7YtOoNBDS0Hx?p=preview
What's the best way to tackle this? Regardless of how I store the info, the underlying issue remains that I can't seem to get the checkbox list to reconcile with what's being stored, to know that something is checked.
Many many thanks in advance for any help!
The real solution is to make a directive like ngTrueValue that supports expressions so that ngModel can register with an object or array when the checkbox is checked. Unfortunately, it currently only supports strings. It is very likely that ngTrueValue will be updated in the near future, so using ngTrueValue will be the correct approach soon.
Currently, you can make an additional object/array to act as the model for the checkboxes and use that set of data to determine what you want to display.
Basic and Generic Example:
Live Demo (click).
Markup:
<div ng-controller="myCtrl1">
<div ng-repeat="item in shared.data track by $index">
<input type="checkbox" ng-model="shared.checks[$index]"/>
<span>{{item.name}}</span>
</div>
</div>
<div ng-controller="myCtrl2">
<h2>Checked:</h2>
<div ng-repeat="item in shared.checks track by $index" ng-show="item===true">{{shared.data[$index].name}}</div>
</div>
JavaScript:
app.controller('myCtrl1', function($scope, myService) {
$scope.shared = myService;
});
app.controller('myCtrl2', function($scope, myService) {
$scope.shared = myService;
});
app.factory('myService', function() {
var myService = {
data: [
{name:'Foo'},
{name:'Bar'},
{name:'Baz'},
{name:'Qux'}
],
checks: []
};
return myService;
});
Your code adapted:
Live demo (click).
Note that I have changed checkboxFactory to checkboxService. Factories return (create) services, so calling the service itself a factory is odd. Also, I got rid of a lot of stuff that either isn't needed or doesn't seem relevant.
JavaScript:
app.controller('Ctrl', function($scope, checkboxService) {
$scope.items = ['city', 'state'];
$scope.selection = $scope.items[0];
$scope.shared = checkboxService;
});
app.controller('AllCtrl', function($scope, checkboxService) {
$scope.shared = checkboxService;
});
app.factory('checkboxService', function() {
var checkboxService = {
city: [
{"id" : 1, "name" : "Firstplace"},
{"id" : 2, "name" : "Second place"},
{"id" : 3, "name" : "Thirdplace"},
{"id" : 4, "name" : "Fourthplace"},
{"id" : 5, "name" : "Fifth place"}
],
state: [
{"id" : 6, "name" : "yellow dog"},
{"id" : 7, "name" : "bluedog"},
{"id" : 8, "name" : "cobalt dog"},
{"id" : 9, "name" : "purple dog"},
{"id" : 10, "name" : "greendog"}
],
checks: {
city: [],
state: []
}
};
return checkboxService;
});
Markup:
<div ng-controller="Ctrl">
FIRST PAGE: make choices<br/>
<select ng-model="selection" ng-options="item for item in items">
</select>
<hr/>
<div ng-switch="" on="selection">
<div ng-switch-when="city">
<input ng-model="searchText">
<div ng-repeat="item in shared.city | filter:searchText track by $index">
<div class="checkbox-box">
<label>
<input type="checkbox" ng-model="shared.checks.city[$index]"/>
<span class="labelText">{{item.name}} {{item.id}}</span>
</label>
</div>
</div>
</div>
<div ng-switch-when="state">
<input ng-model="searchText">
<div ng-repeat="item in shared.state | filter:searchText track by $index">
<div class="checkbox-box">
<label>
<input type="checkbox" ng-model="shared.checks.state[$index]"/>
<span class="labelText">{{item.name}}</span>
</label>
</div>
</div>
</div>
<div ng-switch-default></div>
</div>
</div>
<div ng-controller="AllCtrl">
<hr/>
SECOND PAGE: show choices made<br/>
<div ng-repeat="item in shared.checks.city track by $index" ng-show="item===true">
<div class="selected-box1">{{shared.city[$index].name}} {{item.id}}<i class="fa fa-times-circle fa-lg pull-right"></i></div>
</div>
<div ng-repeat="item in shared.checks.state track by $index" ng-show="item===true">
<div class="selected-box2">{{shared.state[$index].name}}<i class="fa fa-times-circle fa-lg pull-right"></i></div>
</div>
</div>

Resources