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!');
}
}
},
Related
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>'
I have a field that is stateful, and I also have it hooked up to the change event... when its value changes, I want to perform some operation. However, because it's a stateful field, the change event fires when I go back to this view, and unfortunately, the change event fires before the ViewController's init method, which means I will not be able to access my reference lookup.
In the following example, run it, change the date, and then re-run the application... you'll see a console.log that appears for the change, and then for the init. I realize I could set up the handler in the init method, but that just seems silly. I also realize I could create myField as a private var and access it that way, but that also seems silly. And yes, I could change to the select event, but that's not what I want to do. Anyone have any thoughts? Here's an example:
Ext.application({
name : 'Fiddle',
launch : function() {
Ext.state.Manager.setProvider(new Ext.state.CookieProvider());
Ext.define('MyViewController', {
extend: 'Ext.app.ViewController',
alias: 'controller.myView',
init: function() {
console.log('init fired', this.lookupReference('myField'))
},
onChange: function(value) {
console.log('onChange fired', this.lookupReference('myField'));
}
});
Ext.define('MyView', {
extend: 'Ext.container.Container',
controller: 'myView',
renderTo: Ext.getBody(),
items: [{
xtype: 'datefield',
value: new Date(),
stateful: true,
stateId: 'blahblah',
listeners:{
change: 'onChange'
}
}, {
xtype: 'datefield',
value: new Date(),
reference: 'myField'
}]
});
Ext.create('MyView');
}
});
This is because the state mixin is initialized before the controller, this is code taken directly from Ext.Component's constructor:
me.mixins.state.constructor.call(me);
me.addStateEvents('resize');
controller = me.getController();
if (controller) {
controller.init(me);
}
There is no config to change this behavior. Honestly, I've never seen someone make a form field's value stateful.
You can use the buffer config to delay event firing.
This has an advantage of setting up the event after the controller is initialised.
The solution:
listeners: {
change: {
buffer: 300,
fn: 'onChange'
}
}
An Alternative is to handle 'beforestaterestore` event of the stateful field and apply the state value only after controller is initialised.
listeners: {
beforestaterestore: function (field, state){
var controller = field.up().getController();
Ext.Function.interceptAfter(controller, 'init', function(){
field.setValue(state.value); // update
},this);
return false;
}
}
I'm writing custom Angular directives for a new application and unit testing them using Jasmine. However, I can't for the life of me figure out how to get full code coverage (or even 80%) on the Kendo Grid Datasource.
I have a custom Angular Kendo grid directive that looks like this:
function customKendoGrid() {
return {
scope: {
hiPageSize: "="
},
template: "<div kendo-grid k-options='gridOptions' k-ng-delay='gridOptions'></div>",
controller: "hiKendoGridCtrl"
};
}
I did this so I could put a custom object on the scope for the grid directive. My controller looks like this:
function hiKendoGridCtrl($scope, $http, hiKendoGridSvc) {
var initialData = {
page: 1,
pageSize: 2,
type: "initial"
};
if(angular.isUndefined($scope.initialLoad)){
$scope.initialLoad = true;
}
var firstPageData = hiKendoGridSvc.getFirstPage(initialData);
firstPageData.then(function (result) {
var columnSet = result.ColumnSet;
var dataModel = result.Model;
var GridModel = kendo.data.Model.define(dataModel);
var firstPage = result.Data;
var totalResults = result.Total;
$scope.gridOptions = {
dataSource: {
schema: {
data: "Data",
total: function () { return totalResults; }, // NOT COVERED
model: GridModel
},
transport: {
read: function (options) {
if ($scope.initialLoad) {// NOT COVERED
$scope.initialLoad = false;// NOT COVERED
options.success({ Data: firstPage });// NOT COVERED
} else {
var requestData = {// NOT COVERED
page: options.data.page,
pageSize: options.data.pageSize,
type: "page"
};
$http({ method: 'POST', url: 'Home/DataSourceResult', data: requestData }).success(
function (data) {
options.success(data);// NOT COVERED
}).error(
function (data, status, headers, config) {
console.log(data);// NOT COVERED
console.log(status);// NOT COVERED
console.log(headers);// NOT COVERED
console.log(config);// NOT COVERED
});
}
}
},
serverPaging: true,
pageSize: $scope.hiPageSize
},
scrollable: true,
pageable: {
input: true,
numeric: false,
refresh: true
},
editable: true,
columns: columnSet,
sortable: true,
groupable: true
};
});
}
Explanation of above
An initial call is made to the server to get all grid configuration (schema, columns, first page of data, and total). All subsequent calls go to the same URL with different post parameters just retreiving a page of data from the server.
My problem is that I can't seem to find ways to traverse the code paths shown above as "NOT COVERED" in a comment.
I invoke both the grid and the controller in separate unit tests, but can't seem to get a Kendo Grid to compile and invoke the different paths above.
My current two tests for the controller and the directives are:
Controller
beforeEach(inject(function($http){
ctrl = $controller("hiKendoGridCtrl", {
$scope: $scope,
$http: $http,
hiKendoGridSvc: hiKendoGridSvcMOCK
});
$scope.$digest();
}));
And with that I can assert all kinds of things for the controller, including that the correct '$scope.gridOptions' are defined.
Directive Test
// I set up the scope as new rootScope and set compile = $compile in the beforeEach of this test.
it("should output the correct HTML", function () {
catchPOST.respond({
data : responseDataMOCK
});
element = '';
element = compile(element)(scope);
scope.$digest();
expect(element[0].innerHTML).toContain('');
}
But this does not catch the above descriptions in the controller above.
I have also tried something that I consider quirky, but something similar to:
// Execute the above directive test compiling my custom directive to a kendo directive
it("Should have the correct scope", function() {
var gridElement = '<div kendo-grid k-options="gridOptions"></div>';
gridElement = compile(gridElement)($scope);
console.log($scope);
console.log(gridElement);
});
So I thought perhaps I could get the GridOptions on scope and then compile the kendo directive manually, but this doesn't resuly in gridElement having any innerHTML at all.
So... the question is:
How can I add new tests/change existing tests to get full code coverage?
How could I/Should I change my code to make it more etstable? I'm hesitant to do this since it took A LOT of effort to get the grid working correctly with a dynamic configuration.
Thanks!
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
I'm trying to get a custom extjs component to render either a green-check or red-x image, based on a true/false value being bound to it.
There's a couple of other controls that previous developers have written for rendering custom labels/custom buttons that I'm trying to base my control off but I'm not having much luck.
I'd like to be able to use it in a view as follows where "recordIsValid" is the name of the property in my model. (If I remove the xtype: it just renders as true/false)
{
"xtype": "booldisplayfield",
"name": "recordIsValid"
}
Here's what I have so far, but ExtJS is pretty foreign to me.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
renderTpl : '<img src="{value}" />',
autoEl: 'img',
config: {
value: ''
},
initComponent: function () {
var me = this;
me.callParent(arguments);
this.renderData = {
value: this.getValue()
};
},
getValue: function () {
return this.value;
},
setValue: function (v) {
if(v){
this.value = "/Images/booltrue.png";
}else{
this.value = "/Images/boolfalse.png";
}
return this;
}
});
I'd taken most of the above from a previous custom linkbutton implementation. I was assuming that setValue would be called when the model-value for recordIsValid is bound to the control. Then based on whether that was true or false, it would override setting the value property of the control with the correct image.
And then in the initComponent, it would set the renderData value by calling getValue and that this would be injected into the renderTpl string.
Any help would be greatly appreciated.
You should use the tpl option instead of the renderTpl one. The later is intended for rendering the component structure, rather that its content. This way, you'll be able to use the update method to update the component.
You also need to call initConfig in your component's constructor for the initial state to be applied.
Finally, I advice to use applyValue instead of setValue for semantical reasons, and to keep the boolean value for getValue/setValue.
Ext.define('MyApp.view.ux.form.BoolDisplayField', {
extend: 'Ext.Component',
alias : 'widget.booldisplayfield',
tpl: '<img src="{src}" />',
config: {
// I think you should keep the true value in there
// (in order for setValue/getValue to yield the expected
// result)
value: false
},
constructor: function(config) {
// will trigger applyValue
this.initConfig(config);
this.callParent(arguments);
},
// You can do this in setValue, but since you're using
// a config option (for value), it is semantically more
// appropriate to use applyValue. setValue & getValue
// will be generated anyway.
applyValue: function(v) {
if (v) {
this.update({
src: "/Images/booltrue.png"
});
}else{
this.update({
src: "/Images/boolfalse.png"
});
}
return v;
}
});
With that, you can set your value either at creation time, or later, using setValue.
// Initial value
var c = Ext.create('MyApp.view.ux.form.BoolDisplayField', {
renderTo: Ext.getBody()
,value: false
});
// ... that you can change later
c.setValue(true);
However, you won't be able to drop this component as it is in an Ext form and have it acting as a full fledged field. That is, its value won't be set, retrieved, etc. For that, you'll have to use the Ext.form.field.Field mixin. See this other question for an extended discussion on the subject.