I'm using md-select and need to trigger certain code when the value changes (building a Country/State selector). I have it working fine when I change the value through the control but I also need to have the controls reflect the values properly when the model is changed from code. I'm using ng-change to trigger the change code (need ng-change as user can change the value from the keyboard without clicking on it). The problem is that when the value is changed from the code, the event isn't fired. To complicate things a bit more, the md-selects live in a directive to allow me to use the setup in several places.
Here's my directive template:
<md-input-container class="md-block">
<label>Country</label>
<md-select name="country" ng-model="countryState.countryModel" ng-change="countryState.onCountrySelected()">
<md-option ng-repeat="country in countryState.countries" ng-value="country.itemId">
{{ country.name | translate }}
</md-option>
</md-select>
</md-input-container>
<md-input-container class="md-block">
<label>State</label>
<md-select name="state" ng-model="countryState.stateModel" ng-disabled="countryState.countryModel == null">
<md-option ng-repeat="state in countryState.states" ng-value="state.itemId">
{{ state.name | translate }}
</md-option>
</md-select>
</md-input-container>
Here's the directive code:
angular.module('myapp.shared')
.directive('countryStateInput', [function () {
return {
restrict: 'E',
templateUrl: 'app/shared/inputs/countryState/country-state-input.directive.html',
transclude: false,
scope: {
coordsForm: '=',
countryModel: '=',
stateModel: '='
},
bindToController: true,
controllerAs: 'countryState',
controller: ['$scope', '$document', 'OptionService', function($scope, $document, OptionService) {
var ctrl = this;
// Properties
ctrl.countries = null;
ctrl.states = [];
ctrl.selectedCountryIndex = null;
ctrl.onCountrySelected = function() {
// Get the index of the country
for (var i = 0; i < ctrl.countries.length; i++) {
if (ctrl.countryModel === ctrl.countries[i].itemId) {
// If a different country was chosen, clear the selected state
if (i !== ctrl.selectedCountryIndex) {
ctrl.stateModel = null;
angular.copy(ctrl.countries[i].states, ctrl.states);
};
// Save the index of the selected country
ctrl.selectedCountryIndex = i;
return;
}
}
};
// Initialization
var initialize = function () {
OptionService.getCountries().then(
function (result) {
ctrl.countries = result;
});
};
(function () {
$document.ready(function () {
initialize();
})
})();
}]
}
}]);
Here's a usage example:
<country-state-input country-model="app.location.countryId" state-model="app.location.stateId" input-form="locationsForm"></country-state-input>
And OptionService.getCountries() returns something like this (states lists are shortened):
[
{
"itemId": 1,
"name": "CA",
"states": [
{
"itemId": 1,
"name": "CA_ON",
"abbreviation": "ON"
},
{
"itemId": 2,
"name": "CA_QC",
"abbreviation": "QC"
}
]
},
{
"itemId": 2,
"name": "US",
"states": [
{
"itemId": 14,
"name": "US_AL",
"abbreviation": "AL"
},
{
"itemId": 15,
"name": "US_AK",
"abbreviation": "AK"
}
]
}
]
Basically, I'm trying to figure out if there's a way to trigger onCountrySelected that will cover all 3 use cases.
You could use $scope.$watch
$scope.$watch(
function valueGetter(){
return smth;
},
function onChange(newSmth, oldSmth){
}
)
Related
I have a list on ng-repeat with names. I want to take this name and pass to url as parameter to take his subjects. What i have done until now is that i can pass the name and get the subjects from another API. But the problem is that the last name replaces all the dropdowns with his values:
<ul ng-repeat="person in persons">
<li ng-init="pass(person.name)">{{person.name}}</li>
<li>
<select>
<option ng-repeat="(key, value) in choices">{{value}}</option>
</select>
</li>
</ul>
Controller.js
$scope.person = {
[
{
"id": 1,
"name": "Mark"
},
{
"id": 2,
"name": "Jakob"
}
]
}
$scope.pass = function(name) {
$scope.name = name;
$http.get('url?name=' + $scope.name, {})
.then(function(response) {
$scope.choices = response.data;
},
function(errResponse) {
console.error('Error while fetching choices');
});
}
Note: There is a get method to pass the person.name at the end of the URL.
How can it be isolated to pass from HTML to controller.
Thanks
Make your controller code like:
$scope.persons = [{
"id": 1,
"name": "Mark"
},
{
"id": 2,
"name": "Jakob"
}
];
$scope.pass = function(person) {
$scope.name = person.name;
$http.get('url?name=' + $scope.name, {}).then(function(response) {
person.choices = response.data;
},
function(errResponse) {
console.error('Error while fetching choices');
});
}
And your html like:
<ul ng-repeat="person in persons">
<li ng-init="pass(person)">{{person.name}}</li>
<li><select>
<option ng-repeat="(key, value) in person.choices">{{value}}</option>
</select></li>
</ul>
Here's a sample fiddle: https://jsfiddle.net/Lt7aP/4019/
I want to create specific custom filter which will filter object by "colors" I have bigger object but for this case I created small one only to show you what is the issue so I have json like this:
[
{
"nazwa": "koszule",
"colors": [
"white"
],
"rozmiary": [],
"url": "/pl/p/koszule/3693",
"rodzaj": 2,
"img": "/images/nophoto_300x300.jpg",
"muszka": null,
"alt": "koszule"
},
{
"nazwa": "koszule",
"colors": [
"blue"
],
"rozmiary": [],
"url": "/pl/p/koszule/3693",
"rodzaj": 2,
"img": "/images/nophoto_300x300.jpg",
"muszka": null,
"alt": "koszule"
},
{
"nazwa": "koszule",
"colors": [
"white",
"blue"
],
"rozmiary": [],
"url": "/pl/p/koszule/3693",
"rodzaj": 2,
"img": "/images/nophoto_300x300.jpg",
"muszka": null,
"alt": "koszule"
}
]
I also have multiple select in my view
<md-input-container class="md-block" flex-gt-sm>
<md-select ng-change="checkColors()" ng-model="kolorEnd" multiple>
<md-optgroup label="<?php echo $this->translate('kolory'); ?>">
<md-option ng-value="unikalneKoloryWidok" ng-repeat="unikalneKoloryWidok in unikalneKolory | orderBy : expression">{{unikalneKoloryWidok}} </md-option>
</md-optgroup>
</md-select>
</md-input-container>
and in controller is:
$scope.dataParsed = angular.fromJson(prdData);
angular.forEach($scope.dataParsed , function(item , i) {
angular.forEach(item.kolory , function(item , i) {
wszystkieKolory.push(item);
});
});
$scope.unikalneKolory = $.unique(wszystkieKolory);
$scope.checkColors = function(){
$scope.$watch('kolorEnd' , function(oldv,newv) {
$scope.kolory = oldv.toString();
});
};
frost.filter('colorsFlt' , function(){
return function(data,inpArray) {
var output = [];
if (angular.isArray(inpArray)) {
}
}
What I want to achieve is - if someone select only 'white' option show only products which has 'white' color, if customer select 'white' and 'blue' it supposed to show products which has only white and only blue but also products which has ['white','blue'].
I am in a trap:) And don't know how to write my filter to achieve this. Please help;)
So I achieved what I wanted by trying different things:) here is the code in future use:
controller
frost.filter('megaFlt' , function(){
return function(data,firstarg , secarg , filtertype) {
var output = [];
if (angular.isString(firstarg) || angular.isString(secarg)) {
angular.forEach(data,function(el,j) {
$.map(el[filtertype],function(test,i){
if(test == firstarg || test == secarg ) {
return output.push(data[j]);
}
});
});
return output;
}
else {
return data;
}
}
});
view
<md-input-container class="md-block" flex-gt-sm>
<md-select ng-change="checkColors()" ng-model="kolorEnd" multiple>
<md-optgroup label="<?php echo $this->translate('kolory'); ?>">
<md-option ng-value="unikalneKoloryWidok" ng-repeat="unikalneKoloryWidok in unikalneKolory | orderBy : expression">{{unikalneKoloryWidok}} </md-option>
</md-optgroup>
</md-select>
</md-input-container>
and put filter in ng-repeat element
| megaFlt:kolorEnd[0]:kolorEnd[1]:'kolory'
I have an angular directive with a scope.$watch which is not working, but I know the value is changing. Here's the directive:
var StepFormDirective = function ($timeout, $sce, dataFactory) {
return {
replace: false,
restrict: 'AE',
scope: {
currentStep: "=",
title: "="
},
template: '<h3>{{title}}</h3><form class="step-form"></form>',
compile: function (tElem, attrs) {
return function (scope, elem, attrs) {
scope
.$watch(
function(){return scope.currentStep;},
function (newValue) {
var stepFormFields = Array();
stepFormFields.push($sce.trustAsHtml("<p>Fields</p>"));
alert(scope.currentStep);
}
);
};
}
};
};
Here's the tag:
<div title="'test'" currentStep="currentContext.currentStep"></div>
I know the currentContext.currentStep is changing, because I also have this on my page and it updates:
<pre>{{currentContext.currentStep | json}}</pre>
The alert gets called the first time, but then when the value changes (evidenced by the bit in the pre tags) the alert does not get called again and I have no js console errors.
The output for the step (it's data type) is:
{
"identifier": "830abacc-5f88-4f9a-a368-d8184adae70d",
"name": "Test 1",
"action": {
"name": "Approval",
"description": "Approve",
"instructions": "Select 'Approved' or 'Denied'",
"validOutcomes": [
{
"outcome": "Approved",
"display": "Approved",
"id": "Approved"
},
{
"outcome": "Denied",
"display": "Denied",
"id": "Denied"
}
]
...
Try to use $watch method with third argument set by true (objectEquality):
$watch('currentStep', function(newValue){...}, true);
Or use $watchCollection:
$watchCollection('currentStep', function(newValue){...})
Plunkr here
I have a custom directive I'm build to display basic user data. The HTML template is able to render the user data using {{xxx}} syntax, but I also need to use one of the properties from the user data object to retrieve a photo of the user.
My problem is that within the link function the personData value is always null. I added a $observe on person, but it is always null also.
Is there a way observe changes to the personData object and perform action on change?
Directive Code:
app.directive('graphPerson', ['$http', function() {
return {
restrict: 'E',
scope: {
personData: '=person'
},
link: function($scope, element, attrs, ctrl) {
console.log("Directive was linked");
console.log($scope.personData);
attrs.$observe('person', function(newValue, oldValue) {
console.log($scope.personData);
if ($scope.personData) {
//hard-coded photo for demo purposes
$scope.myPhoto = "http://upload.wikimedia.org/wikipedia/commons/thumb/5/53/Blank_woman_placeholder.svg/315px-Blank_woman_placeholder.svg.png";
}
});
},
templateUrl: 'graph-person.html',
};
}]);
I am assuming that your intent is to monitor changes made to the personData object and not to the actual value of the DOM attribute person in your directive. Is that correct?
I would use $scope.$watch() here.
See: http://plnkr.co/edit/SU5x1F5tZKQWk4VK5OGO
app.controller('MainCtrl', function($scope) {
$scope.name = 'World';
$scope.personData = null;
$scope.people = [
{
"displayName": "Katie Jordan",
"givenName": "Katie",
"jobTitle": "Auditor",
"mail": "KatieJ#demo.com",
"mobilePhone": null,
"officeLocation": "12/1110",
"preferredLanguage": "en-US",
"surname": "Jordan",
"userPrincipalName": "KatieJ#demo.com",
"id": "a0da13e4-1866-492e-96e6-8a2a4b60f650"
},
{
"displayName": "James Jordan",
"givenName": "James",
"jobTitle": "Auditor",
"mail": "JamesJ#demo.com",
"mobilePhone": null,
"officeLocation": "12/1110",
"preferredLanguage": "en-US",
"surname": "Jordan",
"userPrincipalName": "JamesJ#demo.com",
"id": "b0da13e4-1866-492e-96e6-8a2a4b60f651"
}
];
$scope.clickMe = function(index) {
$scope.personData = $scope.people[index];
}
});
app.directive('graphPerson', ['$http', function() {
function getImageUrl(id)
{
console.log("I should get the URL For: " + id);
return id;
}
return {
restrict: 'E',
scope: {
personData: '=person'
},
link: function($scope, element, attrs, ctrl) {
console.log("Directive was linked");
$scope.$watch('personData', function(newValue) {
console.log($scope.personData);
if ($scope.personData) {
$scope.myPhoto = getImageUrl($scope.personData.id);
}
});
},
templateUrl: 'graph-person.html',
};
}]);
I'm trying to build a widget to render arbitrary data (javascript object) on ui. And the schema of the data is defined by a json schema file. And the model must be two-way binding since I want use this widget to input/display data.
Is directive the right way to do this? Is there anyone can give me some clue to achieve this?
Yes, a directive is the right way. You can see that the angular-schema-form mentioned in the comments is using directives.
If you'd like to create your own directive. Please have look at the demo below or this jsfiddle.
The example with the comments is probably not the best because there you wouldn't need the two-way binding of the data.
But it should give you an idea how this could work.
How does the code work?
It loops over schema.items.properties and creates a template with ng-model / ng-restrict and all bindings added to the template.
After the loop is done the $compile service is adding the current scope to the template.
Replace the html with the compiled template.
The demo is probably pretty basic compared to the angular-schema-form code but it's easier to understand and easier to modifiy to your needs.
angular.module('demoApp', [])
.controller('mainController', MainController)
.directive('awDisplayJsonView', DisplayJsonView)
.directive('awDateParser', DateParser);
/**
* Translates unix time stamp into readable dates
* from model/view & view/model
*/
function DateParser() {
return {
require: 'ngModel',
link: function(scope, element, attrs, ngModelController) {
ngModelController.$parsers.push(function(data) {
//convert data from view format to model format
return new Date(data).getTime(); //converted
});
ngModelController.$formatters.push(function(data) {
//convert data from model format to view format
return new Date(parseInt(data)).toUTCString(); //converted return UTC
});
}
}
}
function MainController() {
angular.extend(this, {
/*jsonView: {
"text": { // name of model
type: "input",
label: "Two way binded input",
}
},*/
jsonModel: [{
"id": 1,
"name": "Test User",
"text": "I am a comment.",
"date": "1435427542904",
}, {
"id": 2,
"name": "Antother User",
"text": "I am the second comment.",
"date": "1435427605064",
}],
jsonSchema: {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Comments",
"type": "array",
"items": {
"title": "Comment",
"type": "object",
"properties": {
"id": {
"description": "The unique identifier for a comment.",
"type": "number",
"format": "hidden" // not sure if this is the right place
},
"name": {
"title": "Name",
"type": "string"
},
"text": {
"title": "Comment",
"type": "string"
},
"date": {
"title": "Date (format y/m/d hh:mm:ss GMT)",
"type": "string",
"format": "date-time"
}
},
"required": [
"id",
"name",
"text"]
}
}
});
}
function DisplayJsonView($compile) {
var templatesObj = {
string: function (hidden) {
return $('<input/>')
.attr('type', hidden)
.addClass('form-control');
},
checkbox: function (hidden) {
return $('<input/>')
.attr('type', hidden || 'checkbox')
.addClass('form-control');
},
number: function(hidden) {
return $('<input/>')
.attr('type', hidden)
.addClass('form-control');
}
};
function render(schema, model, index) {
var outTemplate = $(document.createDocumentFragment()),
tempTmpl, hidden; // temporary template, hidden input
angular.forEach(schema.items.properties, function (prop, key) {
//console.log(key, prop.type, prop.format, templatesObj[prop.type]);
hidden = prop.format == 'hidden'? 'hidden': null;
tempTmpl = templatesObj[prop.type](hidden); // get template based on type
tempTmpl.attr({
'ng-model':
'model[' + index + '].' + key, // add current model
'ng-required': schema.items.required.indexOf(key) != -1 // check if it is required
});
if (prop.format == 'date-time')
tempTmpl.attr('aw-date-parser', ''); // add directive if we have a date
outTemplate.append($('<div/>').addClass('form-group')
.append(!hidden ? $('<label/>').text(prop.title || key) : null) //add label if not hidden
.append(tempTmpl));
});
//console.log(model, outTemplate);
return outTemplate;
}
return {
restrict: 'EA',
scope: {
//view: '=', // angular schema form does implement this
model: '=',
schema: '='
},
template: '<div>{{schema |json:2}}</div>',
link: function (scope, element, attrs) {
var out = $('<form/>');
angular.forEach(scope.model, function(item, index) {
//console.log(item);
out.append(render(scope.schema, item, index));
});
var compiled = $compile(out)(scope);
//console.log(scope.model, scope.schema);
element.replaceWith(compiled);
}
};
}
DisplayJsonView.$inject = ['$compile'];
body {
padding: 0.5em;
}
.form-group {
padding-bottom: 0.25em;
}
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css" rel="stylesheet"/>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.js"></script>
<div ng-app="demoApp" ng-controller="mainController as ctrl">
<h3>Comments list with schema json directive</h3>
<form name="form" aw-display-json-view="" data-model="ctrl.jsonModel" data-schema="ctrl.jsonSchema" ng-cloak=""></form>
<div ng-cloak>
{{form|json}}
{{ctrl.jsonModel}}
</div>
</div>