I have a very large form, which was getting difficult to read and follow when editing the HTML. I decided that I would try and make the most of AngularJS and use custom directives to compress the amount of repeated text. Here is the original directive I wrote:
app.directive("formField", function () {
return {
restrict: 'E',
scope: {
fieldData: '=field',
fieldName: '=name',
fieldType: '=type'
},
template: <SOME HTML>
}
});
And I would use this directive to add form fields to my page as follows:
<form-field field="some_data" type="text" name="other_data"></form-field>
I was using the type variable to differentiate between dateTime input, text input, numbers, etc, as they were distinguished in my code by only one keyword (by the input's type attribute.)
However now I have encountered a need to include checkboxes, which, thanks to my layout, require significantly different code to be structured properly. Based on this, when the type "checkbox" is passed into the directive I would like to return a different template value. I have tried variations of this kind of thing:
app.directive("formField", function () {
return {
restrict: 'E',
scope: {
fieldData: '=field',
fieldName: '=name',
fieldType: '=type'
},
template: function () {
if(fieldType == 'checkbox') {
return <CHECKBOX HTML>;
} else {
return <REGULAR FIELD HTML>;
}
}
});
This doesn't work. Can anybody tell me how to check the value coming in for the type field so that I can compare it in the directive's returned object? Thanks.
In the template, you can check for the element's attributes.
Your template should look like:
template: function (element, attrs) {
if(attrs.type == 'checkbox') {
return <CHECKBOX HTML>;
} else {
return <REGULAR FIELD HTML>;
}
}
The isolate scope attribute definitions for fieldData, fieldName, and fieldType are available in the template return string (using expressions), but they are not available in the template's logic. Ex:
template: '<p>{{ fieldData }}</p>'
Related
I want to set a watcher that will run a function for that field when a specific value changes in the templateOptions. The normal formly watcher can be used when you want to know that the input has been changed, so that does not work in my case. I have tried expressionProperties to but i cant get that to work eater.
I made a js Bin as an example.
There are two input fields when you mouse over one field templateOptions.mouseOver becomes true and on mouse leave the mouseOver boolean becomes false. What can i do so that when mouseOver changes a function is run?
You simply can put a controller on the field if you want to do it for each individual field, or you can put it in the template itself if the function will be the same for each field, after specifying a function to run in ng-mouseOver. Like This:
formlyConfigProvider.setWrapper([
{
template: [
'editorEnabled: {{to.editorEnabled}}',
'<div ng-mouseover="to.editorEnabled=true; Update()" ng-mouseleave="to.editorEnabled=false; Update()">',
'<formly-transclude></formly-transclude>',
'</div>'
].join(' '),
controller: function($scope) {
$scope.Update = function() {
//Code to run when value changes
}
}
}
]);
Of if you are doing it in each individual field(if the functions you are going to be calling are going to be different for each field) you would do:
vm.fields = [
{
key: 'textField1',
type: 'input',
templateOptions: {
label: 'Input1',
type: 'text',
value:vm.model.textField1,
editorEnabled: false
},
controller: function($scope) {
$scope.Run = function() {
alert('Changed!');
}
}
},
I'm building a chrome extension and have encountered a bug I cannot wrap my head around. The problem is a single object property that becomes null in chromes' storage.
I'm testing this by doing:
console.log("pre-storage", settings);
var obj = {};
obj[storage_key] = settings;
chrome.storage.sync.set(obj, function() {
chrome.storage.sync.get(storage_key, function(data) {
console.log("post-storage", data[storage_key]);
});
});
This is the output:
pre-storage, Object {
...
soundClip: Object {
options: Array[5],
selected: Object {
label: "Soft2",
value: "snd/soft2.wav"
}
}
}
post-storage, Object {
...
soundClip: Object {
options: Array[5],
selected: null
}
}
Storing JSON.parse(JSON.stringify(obj)) instead of obj directly seems to fix this. Anyone have any ideas what might cause this? Any help is appreciated!
Edit: Making a deep copy of obj does not fix it.
Edit2: I should expand on how settings.soundClip is set. I'm using Angular (1.x) and I'm using a custom select directive. The stripped down directive looks like this:
function mySelect() {
return {
restrict: "E",
templateUrl: "mySelect.html",
scope: {
options: "=",
selected: "="
},
link: function (scope) {
scope.select = function (item) {
scope.selected = item;
};
}
}
}
Directive template view (mySelect.html):
<div>
<div ng-repeat="item in options track by $index"
ng-click="select(item)">
</div>
</div>
The properties are then two-way bound like this:
<my-select selected="settings.soundClip.selected"
options="settings.soundClip.options">
</my-select >
Since calling JSON.parse(JSON.stringify(obj)) seems to fix it, my guess is that you're having a problem with encoding your settings object with a variable instead of a string. See the answer here which might help.
Is it possible that the total quota (or per item) is being hit? Consider displaying the runtime.lastError on the set callback to see if there are any error messages.
chrome.storage.sync.set(obj, function() {
console.log('Error', runtime.lastError);
chrome.storage.sync.get(storage_key, function(data) {
console.log("post-storage", data[storage_key]);
});
});
See the limits here chrome.storage.sync.set
I want to use ng-model with an external model-service. This model has two methods: getValue(variable) and setValue(variable).
So in my html I want to be able to do:
<input type="text" ng-model="balance">
Note: balance is not defined on $scope in my controller. And because we are dealing with more then 4000 different variables, I don't want to define them all on $scope.
And then on change it must call the setValue() method of the model. So in my controller I would like to have something like:
$catchAllGetter = function(variable) { // e.g. variable = 'balance'
var value = Model.getValue(variable);
return value;
}
$catchAllSetter = function(variable, value) { // called on change
Model.setValue(variable, value);
}
Is something like this possible with Angular?
My approach is similar to #Dan Prince, but the implementation differs a bit
Create a directive, that accepts name of the model variable, and then inject your model service in the directive itself to perform the getting and setting.
Edit : As specified by #Werlang, writing an attribute that replaces
ngModel will refrain you from features like validation, formatting,
debounced update, ng-change etc. So instead of writing a replacement,
we will instead wire up a supplementary attribute
.
app.directive('dynamicInput', function() {
return {
restrict: 'A',
link: function(scope, el, attr) {
scope.variableName = angular.copy(attr.ngModel); // Saving the variable name
scope[attr.ngModel] = (attr.ngModel + '_1'); // Setting a dummy value in the scope variable.
// In your case it will look something like scope[attr.ngModel] = Model.getValue(attr.ngModel);
scope.$watch(attr.ngModel, function(newValue, oldValue) {
console.log(scope.variableName + " ==> " + newValue);
//Model.setValue(scope.variableName, newValue);
});
}
};
})
Then in your HTML :
<input ng-model='balance' dynamic-input />
You can create a new directive which implements this behaviour.
<input model-getter='getFn()' model-setter='setFn($value)' />
This would be fairly straightforward to implement:
app.directive('modelGetter', function() {
return {
restrict: 'A',
scope: {
get: '&modelGetter',
set: '&modelSetter'
},
link: function(scope, element) {
element.val(scope.get());
element.on('change', function() {
var val = element.val();
scope.set({ $value: val });
});
}
};
})
look at example, i created for you. I hope I have understood you correctly
$scope.$watch('variables', function(newValue) {
console.log("triggers on variables change");
angular.forEach(newValue, function(value, key) {
Model.setValue(key, value);
});
}, true);
ngModel supports getter and setters. Here's how it works:
<input ng-model="balance" ng-model-options="{ getterSetter: true }">
This works if balance is a getter/setter function:
$scope.balance(100); // sets 100
var b = $scope.balance(); // returns 100
You don't need to expose each variable on the scope - you could just expose the Model service that you use in your example:
$scope.Model = Model;
then, in the View, bind to whatever property you need:
<input ng-model="Model.balance" ng-model-options="{ getterSetter: true }">
Have all your variables in an object array:
[
{key: "Variable 1", value: 1, kind: "number"},
{key: "Variable 2", value: "some text", kind: "text"},
{key: "Variable 3", value: new Date(), kind: "date"}
]
Then in your view you shall create them with the help of an ng-repeat:
<div ng-repeat="variable in myVariables">
<input type="{{variable.kind}}" ng-model="variable.value" ng-change="changed(variable)">
</div>
If you need to update your external service, implement a method changed(variable) in your controller.
ES5 Object properties to the rescue:
Object.defineProperty($scope, 'balance', {
enumberable: true,
get: function () {
// -- call your getter here
},
set: function (val) {
// -- call the setter here
}
});
This is native Javascript, so it does not get faster than this.
You can evaluate your model function dynamically, e.g.
<input type="text" ng-model="myModel(var)">
And in the controller:
$scope.myModel = function(var) {
return function(newValue) {
// this is a regular model function but you can use 'var' here
...
}
}
I have written the following directive:
var gameOdds = function(){
return {
template: '{{games["#homeTeam"]}} vs {{games["#awayTeam"]}}',
scope: {
games: '#'
}
};
};
<div game-odds games="{{games}}">
This uses the following JSON data (part of the json is below):
{
#id: "69486",
#homeTeam: "Home Team",
#awayTeam: "Away Team",
otherNormalValues : {
etc: "normal..."
}
}
I know that the method of selecting these keys preceded with an # symbol works when put directly into the HTML bound to a controller. But in my directive I cannot select the fields in this ["#field"] way.
Does anyone know how to do this?
Instead of using the attribute object notation, #, you can use the = instead.
DEMO
JAVASCRIPT
.directive('gameOdds', function() {
return {
template: '{{games.homeTeam}} vs {{games.awayTeam}}',
scope: {
games: '='
}
}
});
HTML
<div game-odds games="games"></div>
Update: Sorry for the late reply, as what the accepted answer had mentioned, you can access them with the [] notation, if the key starts with special characters in it:
.directive('gameOdds', function() {
return {
template: '{{games['#homeTeam']}} vs {{games['#awayTeam']}}',
scope: {
games: '='
}
}
});
The # symbol on scope transforms whatever you pass to the attribute games into text, and passes it into your directive. If you use the = symbol, you can pass a scope variable into the directive.
With #, scope.games will be a string
With =, scope.games will be your json object
var gameOdds = function(){
return {
template: '{{games["#homeTeam"]}} vs {{games["#awayTeam"]}}',
scope: {
games: '='
}
};
};
<div game-odds games="games">
I have the following situation:
A list of indicators, each of them having the properties name, description, essential and differential.
$scope.indicators = [
{ name: 'indicator1' , description: 'blah1', essential: true, differential: false },
{ name: 'indicator2' , description: 'blah2', essential: false, differential: true },
{ name: 'indicator3' , description: 'blah3', essential: true, differential: true },
{ name: 'indicator4' , description: 'blah4', essential: false, differential: false }
]
I'd like to be able to filter with a select the following combinations:
"All", "Essential", "Differential", "Essential and Differential", "Neither Essential nor Differential"
I have tried using ng-model in the select associated with the ng-repeat with | filter, but that ruined the pagination.
I couldn't think of way of using the st-search directive since I'm filtering two properties combined.
Does anyone have a suggestion?
Follows the plunker with the sample code: http://plnkr.co/edit/t9kwNUjyJ15CbLFFbnHb
Thanks!!
The search are cumulative, so if you call the controller api search with multiple you will be able to add the predicates.
Just make sure to reset the sortPredicate object whenever the value changes.
this plunker shows how to write a plugin with your requirements (although I don't understand what all would be in this context
.directive('customSearch',function(){
return {
restrict:'E',
require:'^stTable',
templateUrl:'template.html',
scope:true,
link:function(scope, element, attr, ctrl){
var tableState=ctrl.tableState();
scope.$watch('filterValue',function(value){
if(value){
//reset
tableState.search.predicateObject ={};
if(value==='essential'){
ctrl.search('true', 'essential');
} else if(value === 'differential'){
ctrl.search('true', 'differential')
} else if(value === 'both'){
ctrl.search('true','essential');
ctrl.search('true', 'differential');
} else if(value === 'neither'){
ctrl.search('false','essential');
ctrl.search('false', 'differential');
}
}
})
}
};});
This is how I would do it.
In your controller define an Array with the possible options and the filter for each option, like this:
$scope.options = [
{value:'All', filter:{}},
{value:'Essential', filter:{essential:true}},
{value:'Differential', filter:{differential:true}},
{value:'Essential and Differential', filter:{differential:true, essential:true}},
{value:'Neither Essential nor Differential', filter:{differential:false, essential:false}}
];
Then in your html declare your select using this array, like this:
<select ng-model='diffEssFilter' ng-options='option.value for option in options'>
</select>
And then in your ng-repeat use the filter that would be stored in diffEssFilter, like this:
<tr ng-repeat="indicator in indicators | filter:diffEssFilter.filter">
That's it.
Working example