rendering a dynamic placeholder with angular - javascript

I've looked around, found several resources labeled 'ng-placeholder' or something incredibly similar. I cannot get this to work:
<input type="text" placeholder="{{option.name}}" class="form-control" ng-switch-when="text" />
I've noticed there doesn't appear to be anything on the input documentation as well. I'm pretty new to angular, and this has done nothing but frustrate me for a few hours. There must be a way to do this.

Why not write your own directive for ng-placeholder? Something simple like this should work. You can call it in your html like this
<input ng-placeholder='test'>
Where test is a scope variable in the current controller.
.directive('ngPlaceholder', function($document) {
return {
restrict: 'A',
scope: {
placeholder: '=ngPlaceholder'
},
link: function(scope, elem, attr) {
scope.$watch('placeholder',function() {
elem[0].placeholder = scope.placeholder;
});
}
}
});

There exists a conditional attribute property in AngularJS ng-attr-{property_name}
For example, I'm using different placeholders for different search terms using
ng-attr-placeholder="{{isAdvanceSearch ? setPlaceholder(searchOption) : 'John Smith, 08/23/1970, 123'}}"
Here on the basis of isAdvanceSearch variable, I'm setting different placeholders in setPlaceholder method.
setPlaceholder method returns the placeholder to set in the input field.
$scope.setPlaceholder = function(searchOption) {
if (searchOption === "foo") {
return "Search by foo… e.g. foo1";
} else if (searchOption === "bar") {
return "Search by bar… e.g. bar123";
} else {
return "John Smith, 08/23/1970, 123";
}
};
Note: John Smith, 08/23/1970, 123 is the default placeholder.
Don't forget to wrap the expression in the {{}} brackets.

Related

How to create a custom angular directive that will override ng-disabled?

First some background: My application allows users to control whether or not fields are required, disabled, etc. through an admin tool. I have a service that takes a field name and returns me the user defined rules in a format like this:
{
"disabled" : true,
"required" : true
}
I want a custom attribute directive that will control these properties on an input field using the service. I would expect the usage to look something like this:
<input type="text" my-rule="fieldName" ng-model="myfield" />
I'm able to accomplish this easily with a directive like the following:
angular.module('app').directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
$http.get('/api/rule/'+scope.myRule).success(function(rule) {
if (rule.disabled) {
element.attr('disabled','disabled');
}
if (rule.required) {
element.attr('required','required');
}
});
}
}
}
]);
My problem is that if a user does not have a field disabled I may still want to disable it until, for example, another field has been filled out. This should be easy to do with ng-disabled:
<input type="text" my-rule="fieldA" ng-model="fieldA" />
<input type="text" my-rule="fieldB" ng-model="fieldB" ng-disabled="!fieldA" />
However, this does not work because if the user chooses to disable fieldB then the field should always be disabled regardless of the ng-disabled attribute but instead the ng-disabled attribute overrides the user's rule. I tried something like this to remove the ng-disabled if the field is disabled by the user but that does not seem to have an effect:
angular.module('app').directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
$http.get('/api/rule/'+scope.myRule).success(function(rule) {
if (rule.disabled) {
element.attr('disabled','disabled');
element.removeAttr('ng-disabled');
}
if (rule.required) {
element.attr('required','required');
element.removeAttr('ng-required');
}
});
}
}
}
]);
This removes the attribute but it seems at that point it is too late and the field still becomes enabled as soon as fieldA is filled in.
How can I dynamically remove the ng-disabled attribute in my custom directive so that it no longer has an effect on the field?
Update:
I added a code snippet demonstrating my problem.
angular.module('app',[]).directive('myRule', ['$http',
function($http) {
return {
restrict: 'A',
scope: {
myRule: '#'
},
link: function(scope, element, attrs) {
// would normally be making an ajax call to get the rule
var rule = { disabled: scope.myRule != "fieldA" };
if (rule.disabled) {
element.attr('disabled','disabled');
element.removeAttr('ng-disabled');
}
}
}
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app">
<p>Field B and Field C have been disabled by the user but since Field C includes an ng-disabled expression it will be incorrectly enabled when Field A is filled out.</p>
<input type="text" my-rule="fieldA" ng-model="fieldA" placeholder="Field A" />
<input type="text" my-rule="fieldB" ng-model="fieldB" placeholder="Field B" />
<input type="text" my-rule="fieldC" ng-model="fieldC" placeholder="Field C" ng-disabled="!fieldA" />
</div>
Tryprop('disabled', boolean) instead of attr(). This will change the element property which is not always the same as the attribute.
Since you are manipulating the DOM outside of angular you should probably tell angular to run a digest also by calling scope.$apply() or $timeout()
Not sure this will work and I think you will probably need a directive to wrap the whole input.
One suggestion is take a look at angular-formly which builds whole forms including conditional validation from object models

Does AngularJS store a value in the $error.maxlength object?

I've got a UI page setup through Angular, and I'm trying to take advantage of the built in ng-maxlength validator on an input element. Long story short, I know about $scope.form.$error and how that object has a maxlength property in the case that the validation fails. But I want to display an error message specific to the character length that was violated, and I don't see anywhere that the length that I specified was stored on this object. Does anyone know if it's possible to access this, so I don't have to write out a separate error message for each input that has the max length violated?
EDIT: To answer your question, yes angular does store a boolean value in the $error object that is accessible to your via the key(s) that are set in the object. In the case of the code I provided below and in th jsFiddle, we are setting the key for angular, and the value of either true or false.
Be mindful when setting the value as it is reversed. ex. $setValidity( true ), flips the $error to false.
Ok, here is what I think you were looking for...
In Angularjs v1.2.13 you will not have access to ng-message or the $validator pipeline,
which is why are are using $formatters and $parsers.
In this case, I am using named inputs, but perhaps in your case you need dynamic input names?
Plus, if you are using inputs but no form, then getting the error message to display would have to be done with a separate custom directive.
If so, then please look here for dynamically named input fields for some help.
dynamic input name in Angularjs link
Let me know if this works; I'll make changes as needed to HOOK YOU UP!
In case you don't know, you can write over Angular's maxlength for each individual input.
If you changed 'maxlength' in the updateValidity() function in the directive below, to something like 'butter', then $scope.form.inputname.$error would be something like
$scope.formname.inputname.$error { butter: true }
if you also used ng-maxlength="true", then it would be
$scope.formname.inputname.$error { butter: true, maxlength: true }
Another example if you used ng-maxlength, and capitalized the 'maxlength' in the directive to 'Maxlength'
Then you would get
$scope.formname.inputname.$error { maxlength: true(angular maxlength), Maxlength: true(your maxlength)
And of course if you name it the same, then yours writes over angulars
$scope.formname.inputname.$error { maxlength: true };
The point is YOU can add your own names to the angular $error object; you can write over Angular's; and you can just use what Angular gives you when you use Angular's directives: like ng-required="true", or ng-maxlength="true"
Link to YOUR angularjs version on jsFiddle
jsFiddle LInk
<div ng-app="myApp">
<form name="myForm">
<div ng-controller="MyCtrl">
<br>
<label>Input #1</label>
<br>
<input ng-model="field.myName" name='myName' my-custom-length="8" />
<span ng-show="myForm.myName.$error.maxlength">
Max length exceeded by {{ myForm.myName.maxlength }}
</span>
<br>
<br>
<label>Input #2</label>
<br>
<input ng-model="field.myEmail" name='myEmail' my-custom-length="3" />
<span ng-show="myForm.myEmail.$error.maxlength">
Max length exceeded by {{ myForm.myEmail.maxlength }}
</span>
</div>
</form>
</div>
var app = angular.module('myApp', []);
app.controller('MyCtrl', function ($scope) {
$scope.field = {};
});
app.directive("myCustomLength", function () {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ctrl) {
if (!ctrl) { return } // ignore if no ngModel controller
ctrl.$formatters.push(validateInput);
ctrl.$parsers.unshift(validateInput);
function validateInput(value) {
if (!value) {
updateValidity(false);
return;
}
inputLength(value);
var state = value.length > attrs.myCustomLength;
updateValidity(state);
}
function inputLength(value) {
ctrl.maxlength = null;
var length = value.length > attrs.myCustomLength;
if (length) {
ctrl.maxlength = (value.length - attrs.myCustomLength).toString();
}
}
function updateValidity(state) {
ctrl.$setValidity('maxlength', !state);
}
} // end link
} // end return
});
CSS Here if you need it.
input.ng-invalid {
border: 3px solid red !important;
}

AngularJS : Compile directives inside HTML returned by an API

So I have access to a REST API that I am hitting, that returns the following pre-generated HTML:
<p class="p">
<sup id="John.3.16" class="v">16</sup>
<span class="wj">“For </span>
<span class="wj">God so loved </span>
<span class="wj">the world,</span>
<span class="wj">that he gave his only Son, that whoever believes in him should not </span>
<span class="wj">perish but have eternal life.</span>
</p>
This has presented an interesting new challenge for me in my learning of AngularJS. I have no control over the HTML that is returned from the API, since it's not an API that I built.
What I'm trying to do (and this could be the completely wrong approach) is to build a class directive on the "v" class, so that I can add an ng-click attribute to the verse number and pass the verse information on to another part of my application.
Below is the code I currently have, which doesn't seem to do anything, though I thought it would.
var app = angular.module('ProjectTimothy');
app.filter("sanitize", ['$sce', function($sce) {
return function(htmlCode){
return $sce.trustAsHtml(htmlCode);
}
}]);
app.controller("timothy.ctrl.search", ['$scope', '$http', function($scope, $http){
$scope.url = "http://apiendpoint.com/";
$scope.copyright = "";
$scope.search = function() {
// Make the request to the API for the verse that was entered
// Had to modify some defaults in $http to get post to work with JSON data
// but this part is working well now
$http.post($scope.url, { "query" : $scope.searchwords, "version": "eng-ESV"})
.success(function(data, status) {
// For now I'm just grabbing parts of the object that I know exists
$scope.copyright = data.response.search.result.passages[0].copyright;
$scope.result = data.response.search.result.passages[0].text;
})
.error(function(data, status) {
$scope.data = data || "Request failed";
$scope.status = status;
});
};
}]);
app.directive("v", ['$compile', function($compile) {
return {
restrict: 'C',
transclude: true,
link: function(scope, element, attrs) {
element.html("<ng-transclude></ng-transclude>").show();
$compile(element.contents())(scope);
},
scope: { id:'#' },
/*template: "<ng-transclude></ng-transclude>",*/
replace: false
};
}]);
HTML Template that is being populated with the HTML returned by API:
<div class="bible_verse_search_container" ng-controller="timothy.ctrl.search">
<div class="input-group">
<input type="text" class="form-control" placeholder="Bible Verse To Read (i.e. John 11:35)" ng-model="searchwords">
<span class="input-group-btn">
<button class="btn btn-default" type="button" ng-click="search()">Search</button>
</span>
</div>
<div class="well" ng-show="copyright" ng-bind-html="copyright | sanitize"></div>
<div ng-bind-html="result | sanitize"></div>
</div>
So What I was hoping would happen would be that the HTML is populated into the bottom div that binds the html, and then somehow $compile would be called to convert the "v" class sup's into directives that I can modify. Again, I'm pretty new to Angular, so there may be a super easy way to do this like most other things in Anguler that I just haven't found yet.
Really, the end goal is that each verse number is converted into a directive of its own to be able to make it clickable and access the id attribute that it has so that I can send that information with some user content back to my own API.
This feels like a lot of information, so let me know if anything is unclear. I'll be working on it, so if I figure it out first, I'll be sure to update with an answer.
IN PROGRESS
Checked out this question: https://stackoverflow.com/a/21067137/1507210
Now I'm wondering if it would make more sense to try and convert the section where the verse is displayed into a directive, and then making the search controller populate a scope variable with the HTML from the server, and then use that as the template for the directive... think think think
I think your second approach--convert the section where the verse is displayed into a directive--would be a nice way of doing it.
You could replace this:
<div ng-bind-html="result | sanitize"></div>
with a directive like this:
<verse-display verse-html="{{result}}"></verse-display>
The directive definition would look like this:
app.directive('verseDisplay', ['$compile', function($compile) {
function handleClickOnVerse(e) {
var verseNumber = e.target.id;
// do what you want with the verse number here
}
return {
restrict: 'E',
scope: {
verseHtml: '#'
},
replace: true,
transclude: false,
template: '<div ng-bind-html="verseHtml | sanitize"></div>',
link: function(scope, element, attrs) {
$compile(element.contents())(scope);
element.on('click', '.v', handleClickOnVerse);
}
};
}]);
So you could apply your own click handler to the element.
Here's a fiddle. (Open the console to see the verse number getting logged out.)
Probably the most unwisest of things I have posted here, but it's pretty cool code. I do not know if I recommend actually running this, but here's a jsfiddle
So one of the reasons I call this unwise is because the injected code will run any directives you have not just the one you wanted. There may be many other security risks beyond that as well. But it works fantastically. If you trust the HTML you are retrieving then go for it.
Check out the fiddle for the rest of the code:
function unwiseCompile($compile) {
return function (scope, element, attrs) {
var compileWatch = scope.$watch(
function (scope) { return scope.$eval(attrs.unwiseCompile); },
function (unwise) {
element.html(unwise);
$compile(element.contents())(scope);
// for better performance, compile once then un-watch
if (scope.onlyOnce) {
// un-watch
compileWatch();
}
});
};
}

Use a $formatter on a object typed ngModel value with angular 1.2 worked, but not with version 1.3

This code worked with angular-1.2.26, but not with angular-1.3.0.rc5 (or any 1.3.x versions I tried).
I found this issue https://github.com/angular/angular.js/issues/9218 on angular's github, but I am not familiar with github interface and I cannot figure out if the bug is confirmed or if the behavior is expected, if it has been fixed or not; and if yes, what version should I take.
JSFiddles :
with angular v1.2.26, ngModel as object
with angular v1.3.0.rc5, ngModel as object
For each, I expect to have 'my label' in the input when loading the page. It works for the first one, but not for the second.
And look at the console to see what value is passed to the formatter.
HTML :
<div ng-controller="ctrl as c">
<input my-dir ng-model="c.foobar" />
<pre>{{c.foobar | json}}</pre>
</div>
JS :
var app = angular.module('app', []);
app.controller('ctrl', function(){
this.foobar = {
value : 'my value',
label : 'my label'
}
})
.directive('myDir', function(){
return {
restrict :'A',
require:'ngModel',
link : function(scope, elt, attrs, modelCtrl){
// conversion "view -> model"
modelCtrl.$parsers.unshift( function(value){
console.log('Value:', value);
return {label:value, value:value};
})
// conversion "model -> view"
modelCtrl.$formatters.unshift(function formatter(modelValue){
console.log('modelValue:', modelValue);
return modelValue.label;
})
}
}
})
In 1.3 you should be doing it like this (which will also work in 1.2):
.directive('myDir', function(){
return {
restrict :'A',
require:'ngModel',
link : function(scope, elt, attrs, modelCtrl){
// conversion "view -> model"
modelCtrl.$parsers.push( function(value){
console.log('Value:', value);
return {label:value, value:value};
})
// conversion "model -> view"
modelCtrl.$formatters.push(function formatter(modelValue){
console.log('modelValue:', modelValue);
return modelValue.label;
})
}
}
})
Because if you unshift your $formatter in 1.3, then you will get the stringified value of the model, if you want to have access to the non stringified value of the model, you will have to put your $formatter at the end (push).
I know that this contradicts this comment of Igor Minar.
The breaking change is that the viewValue passed into formatters will
be a toString version of the formatted modelValue. So if any custom
formatters execute after the default formatter, they'll see the string
version of the value. If any formatter needs access to the value
before it was stringified, the formatter should be registered via
$formatters.unshift(customFormatter).
But things changed after that comment.
Example

How to use function in Kendo Grid Column Template with AngularJS

I have a column in a Kendo grid that I want to perform some specific logic for when rendering, and am using Angular. I have the grid columns set up using the k-columns directive.
After looking at the documentation, it seemed simple: I could add the template option to my column, define the function to perform my logic, and pass the dataItem value in. What I have looks something like this:
k-columns='[{ field: "Name", title: "Name",
template: function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}
}]'
However, running this causes a syntax error complaining about the character '{' that forms the opening of the block in my function.
I have seen several examples of defining a template function in this format. Is there something else that needs to be done for this to work? Am I doing something incorrectly? Is there another way of defining the template as a function and passing the column data to it? (I tried making a function on my $scope, which worked, except I couldn't figure out how to get data passed into the function.)
Thank you for your help.
It appears that defining a column template in this fashion isn't supported when using AngularJS and Kendo. This approach works for projects that do not use Angular (standard MVVM), but fails with its inclusion.
The workaround that a colleague of mine discovered is to build the template using ng-bind to specify a templating function on the $scope, all inside of a span:
template: "<span ng-bind=templateFunction(dataItem.Name)>#: data.Name# </span>"
This is the default column templating approach that is implemented by Telerik in their Kendo-Angular source code. I don't know yet if the data.Name value is required or not, but this works for us.
Warning: Don't have access to Kendo to test the code at the moment, but this should be very close
In your case, you are assigning a a string to the value of k-columns and that string contains the the word function and your curly brace {
You need to make sure the function gets executed ... something like this:
k-columns=[
{
field: "Name",
title: "Name",
template: (function (dataItem){
// Perform logic on value with dataItem.Name
// Return a string
}())
}
];
Note the difference:
We create an object -- a real honest-to-goodness object, and we use an IIFE to populate the template property.
Maybe, it will be useful for someone - this code works for me too:
columns: [
{
field: "processed",
title:"Processed",
width: "100px",
template: '<input type="checkbox" ng-model="dataItem.processed" />'
},
and you get the two-way binding with something like this:
<div class="col-md-2">
<label class="checkbox-inline">
<input type="checkbox" ng-model="vm.selectedInvoice.processed">
processed
</label>
</div>
This can be done via the columns.template parameter by supplying a callback function whose parameter is an object representing the row. If you give the row a field named name, this will be the property of the object you reference:
$("#grid").kendoGrid({
columns: [ {
field: "name",
title: "Name",
template: function(data) {
return data.name + "has my respect."
}
}],
dataSource: [ { name: "Jane Doe" }, { name: "John Doe" } ]
});
More information is available on Kendo's columns.template reference page.
After hours of searching. Here is the conclusion that worked:
access your grid data as {{dataItem.masterNoteId}} and your $scope data as simply the property name or function.
Example
template: '<i class="fa fa-edit"></i>',
I really hope this safes somebody life :)
just use like my example:
}, {
field: "TrackingNumber",
title: "#T("Admin.Orders.Shipments.TrackingNumber")",
//template: '<a class="k-link" href="#Url.Content("~/Admin/Shipment/ShipmentDetails/")#=Id#">#=kendo.htmlEncode(TrackingNumber)#</a>'
}, {
field: "ShippingMethodName",
title: "#T("Admin.Orders.Shipments.ShippingMethodName")",
template:function(dataItem) {
var template;
var ShippingMethodPluginName = dataItem.ShippingMethodPluginName;
var IsReferanceActive = dataItem.IsReferanceActive;
var ShippingMethodName = dataItem.ShippingMethodName;
var CargoReferanceNo = dataItem.CargoReferanceNo;
var ShipmentStatusId = dataItem.ShipmentStatusId;
if (ShipmentStatusId == 7) {
return "<div align='center'><label class='label-control'><b style='color:red'>Sipariş İptal Edildi<b></label></div>";
} else {
if (ShippingMethodPluginName == "Shipping.ArasCargo" || ShippingMethodPluginName == "Shipping.ArasCargoMP") {
template =
"<div align='center'><img src = '/content/images/aras-kargo-logo.png' width = '80' height = '40'/> <label class='label-control'><b>Delopi Aras Kargo Kodu<b></label>";
if (IsReferanceActive) {
template =
template +
"<label class='label-control'><b style='color:red; font-size:20px'>"+CargoReferanceNo+"<b></label></div>";
}
return template;
}

Categories

Resources