I come from a Rails background and am attempting to use AngularJS with Rails. I am stuck on a very simple thing: how does one construct what rails calls 'virtual attributes' in an Angularjs environment? Let me give an example.
I have a rails model, called Medication, and it has two attributes, dosage and count. I want to have a 'total' virtual attribute that returns dosage * count. This is trivial in Rails, since it is just an instance method of class Medication.
So, how does that map into the AngularJS world?
Clarification: I am using an ng-repeat directive on Medications (plural) for which I have a MedicationsController. For each ng-repeat loop, I get a 'medication' object, where I want to apply this 'instance method'
total = function() { return dosage * count }
How is that coded? Where do I put the function?
It is really simple with AngularJS as you can use any JavaScript function in AngularJS view. So just define a function on your object (or directly on a scope). For example, give a controller like this:
var MedicationCtrl = function($scope) {
$scope.medication = {
dosage : 0.5,
count : 10,
total : function() {
return this.dosage * this.count;
}
};
}
You can simply write:
Total: {{medication.total()}}
Here is a jsFiddle: http://jsfiddle.net/4r5g5/1/
Then, if you've got a collection of items and don't want to put any logic on an object level, you can define this method on a controller like so:
var MedicationCtrl = function($scope) {
$scope.total = function(medication) {
return medication.dosage * medication.count;
};
};
and use this function from a markup like follows:
<ul ng-repeat="m in medications" ng-controller="MedicationCtrl">
<li>{{m.dosage}} + {{m.count}} = {{total(m)}}</li>
</ul>
And the above code in the jsFiddle: http://jsfiddle.net/4r5g5/3/
Related
I have a angularJS function which returns a value and would like to create an if statement in javascript. I've tried the following but so far no luck.
AngularJS:
$scope.orderTotal = function(index) {
var orderTotal = 0;
angular.forEach($localStorage.items, function(items) {
orderTotal += items.quantity;
})
return orderTotal;
};
Javascriptfile.js:
jQuery(document).ready(function() {
document.write(orderTotal());
}
or:
if (orderTotal() > 10) {
document.write(orderTotal());
}
Is it possible to do something like this and what would it look like?
RvdM - "Because adding and removing classes on divs is something I do with javascript I need to have some variables defined in angularjs available in javascript. Are there better ways to accomplish this?"
Yes, to add and remove classes on elements in AngularJS, use the ng-class directive.
From the Docs:
ngClass
The ngClass directive allows you to dynamically set CSS classes on an HTML element by databinding an expression that represents all classes to be added.
-- AngularJS ng-class Directive API Reference.
You can - I'd question why, but oh well. You can use angular.element and .scope to get the current scope your function is contained in.
For example, say your above function was inside the controller MyController
<div id="controller" ng-controller="MyController"></div>
The JS you'd use is:
var scope = angular.element(document.getElementById("controller")).scope();
scope now has access to all scope methods:
var total = scope.orderTotal();
the DOM :
<div id="mycontroller" ng-controller="mycontroller"></div>
In jquery you do this and it will access that controller and call that function
var orderTotal;
$('#mycontroller').load = function(){
orderTotal = angular.element('#mycontroller').scope().orderTotal();
}
If a "complete" callback is provided, it is executed after post-processing and HTML insertion has been performed
Here you go :)
I am struggling with what I know is a very basic question related to variable declaration. I've read everything I can find on variables but I don't know if my problem is related to 1) how I am declaring variables or 2) how I am setting the scope of the variables.
To start, my understanding of variables in Meteor is that if I use var, then I am setting file-scope, which would make that variable available to every helper for that particular template. If I do not use var, it will be global and therefore available to the helpers in every template. Is that correct?
The following block of code works fine, returning the correct value in the client:
Template.CompanyFinancials.helpers({
priceEarningsFy1: function () {
var compTicker = this.ticker
var price = Companies.findOne({ticker: compTicker}).capTable.lastClose;
var epsFy1 = Companies.findOne({ticker: compTicker}).fy1.eps;
return (price / epsFy1).toFixed(1)
});
I have dozens of similar calculations throughout this app and many which rely on more variables than this example, so I have been trying to factor out the variables and reuse them in the template, like so:
var compTicker = function() {
return this.ticker;
};
console.log(compTicker);
var price = function(compTicker) {
Companies.findOne({ticker: compTicker}).capTable.lastClose;
};
console.log(price);
var epsFy1 = function(compTicker) {
Companies.findOne({ticker: compTicker}).fy1.eps;
};
console.log(epsFy1);
Template.CompanyFinancials.helpers({
priceEarningsFy1: function (price, epsFy1) {
return (price / epsFy1).toFixed(1)
}
});
With this code, console.log() actually returns the text within each function (e.g., return this.ticker) for each variable, not the value. If I declare the variables without functions, like I’ve done within the helper, it returns undefined for compTicker.
I tried to follow this answer which explains reusable code, but not clear if same use case applies. My variables point to specific fields in the database, not necessarily calculations.
Can anyone help me repair my syntax? I'm writing multiples more code than I need to with my current understanding. Thank you.
EDIT
I also tried declaring the variables the same way they are declared in the helper, but these return undefined.
var compTicker = this.ticker;
console.log(compTicker);
var price = CompaniesFeed.findOne({ticker: this.ticker}).capTable.lastClose;
console.log(price);
var epsFy1 = CompaniesFeed.findOne({ticker: this.ticker}).fy1.eps;
console.log(epsFy1);
RESOLUTION:
Using global helpers and returning multiple values, then using dot notation to access in the template HTML:
Template.registerHelper('priceEarnings',function(){
var ticker = this.ticker;
var company = CompaniesFeed.findOne({ticker: ticker});
return {
peFy1: (company.capTable.lastClose / company.financial.fy1.eps).toFixed(1),
peFy2: (company.capTable.lastClose / company.financial.fy2.eps).toFixed(1)
};
});
<td>{{priceEarnings.peFy1}}x</td>
You might be looking for global helpers. These are helpers which can be reused across all templates.
For your priceEarningsFy1 function for example:
Template.registerHelper('priceEarningsFy1',ticker => {
const company = Companies.findOne({ticker: ticker});
return ( company.capTable.lastClose / company.fy1.eps ).toFixed(1);
});
In this case I've specified that ticker is to be provided as an argument. From a blaze template you would use {{priceEarningsFy1 this.ticker}} for example. To refer to this function from js code use UI._globalHelpers.priceEarningsFy1(ticker)
Note that any local functions you define inside a given file are available to any other functions inside the same file. My pattern is to put all my global helpers in one file sorted by name and then at the bottom add various utility functions for use by the global helpers. This keeps things relatively dehydrated.
I'm creating a ViewData in an ASP.Net Controller like this:
var elements = db.Requests.GroupBy(user => user.submitUser)
.Select(slt => new { UsersCount = slt.Key, CountReq = slt.Count() });
this will have for example
UsersCount Count
user1 3
user2 3
user3 3
Then in Javascript I want to create an array with UsersCount and Count, for example:
var arrayUsers = ViewData.UsersCount;
var arrayCounter = ViewData.CountReq;
I already tried several solutions and I can't do it. What's the best solution?
CONTROLLER:
public ActionResult Statistics()
{
var elements = db.Requests.GroupBy(user => user.submitUser)
.Select(slt => new { UsersCount = slt.Key, CountReq = slt.Count() });
ViewData["usersCounter"] = elements;
return View();
}
Javascript in VIEW
<script>
var check = #ViewData["usersCounter"]**;**error
....
}
To use ViewData in a javascript tag just prefix ViewData with the # symbol
var check = #ViewData["array"]
In your controller
ViewData["array"] = elements;
Why not just set the variable to your model since it looks like you're setting it to that
var check = #Html.Raw(Json.Encode(Model));
Then you can do whatever
var blur = check.UserName
The code
#ViewData["usersCounter"]
will effectively do this:
#ViewData["usersCounter"].ToString()
You're setting your viewdata to the result of a select, so the .ToString wil output the generic generic type name directly to the output.
Simply check the source code of the rendered page to see what is being rendered.
Also, specify the 'error' that you get.
If you only change your code to
var check = '#ViewData["array"]'
(as per other answers) then 'check' will not contain the list, only the list.tostring()
If you want to copy the whole list into a js variable, you'll need to serialise it first, eg, something like (with JSON nuget package installed):
var check = #(Newtonsoft.Json.JsonConvert.SerializeObject(ViewData["array"]));
To note: many people will recommend that you use a ViewModel rather than ViewData as it's strongly typed as generally easier to work with / maintain.
However, is there as reason you need/want this in javascript? The GroupBy and Select, even after converting to js aren't really js, and are better suited for working with c# or #{} statements.
Andre have you tried
var check = '#ViewData["array"]'
I'm trying to implement filtering on my input element.
I want to make filtering for input with type="text" field.
For instance, if the model contain more than available characters than I want to change my input value.
I've created jsfiddle
I have directive that generate html template dynamically and it contains input field.
var app = angular.module('app', [])
.controller('ctrlr', function($scope){
$scope.myModel = "test";
$scope.availableCharacters = 5;
$scope.$watch('myModel', function(newValue, oldValue){
if(!newValue){
return;
}
if(newValue.length > 5){
$scope.cutString();
}
});
$scope.cutString = function(){
var length = $scope.myModel.length;
var string = $scope.myModel.slice(0, $scope.availableCharacters);
var countStars = length - string.length;
$scope.myModel = $scope.createStars(string, countStars);
}
$scope.createStars = function(string, countStars){
for(var i = 1; i <= countStars; i++){
string = string+'*';
}
return string;
}
})
.directive('awesome' , function(){
return {
restrict:'E',
template:'<input type="text" ng-model="myModel" ng-value="myModel | filter:my" />'
}
})
Could it possibly to move my code into the filter function? I have a lot of business logic and I don't want to keep my code in the controller, because it will be reusable directive.
I think that implementing this part of functionality as a filter is not the best idea.
It would be much more dynamic if you will implement it as directive on your input element like:
<input type="text" ng-model="myModel" ng-value="myModel" max-length="20" />
In this case it would be more flexible. You will be able to pass an argument into directive (for example length of acceptable value).
Also it is not really readable for another developers to make your input as a template of your directive because of you are using model as attribute of input field and not binding it from directive.
Is there any reason why you use directive to render simple input?
If not, then just live it as input in your view and add directive instead of filter to work with data checks limitations.
Another approach is to implement custom form controls. That will allows you to control incoming and out-coming data.
Here is a example from documentation - Implementing custom form controls (using ngModel)
I'm trying to bind a text input to an attribute within a directive, the reason being I don't want to have to introduce a controller each time I add a new directive of this type. Is this even possible or do you always have to specify a controller when using ng-model. Code example is worth a thousand words so please take a look at http://codepen.io/scottwio/pen/HKqiw . As you type in the input the amount should change.
There are two issues with your code:
scope.$watch(attrs.amount, function (v) {...}); <=>
scope.$watch('100', function (v) {...});
which is never going to change, so does not do what you want.
Since the elements attribute is never going to change, function draw(aniTime) { var amount = attrs.amount; is not so usefull.
You can fix them like this:
scope.$watch('amount', function (v) {...});
and
function draw(aniTime) {
var amount = scope.amount;
...
See, also, this short demo.
If you want to share the specified amount with the parent scope, then you need to set up a two-way data-binding and specify a property in the parent scope to bind to. E.g.:
// Parent scope
$scope.someAmount = 100;
// In HTML
<custommarc amount="someAmount" ...
// In directive
scope: {
amount: '='
...