Using money-rails with AngularJS - ruby-on-rails

I'm converting some forms within my Rails 3.2 app to use AngularJS so that I can do live calculations and such. In my rails app I use money-rails to handle the currency. This treats all currency fields as integers made of of cents.
This becomes a problem when I send all the information through JSON over to my AngularJS template. Now my form is all in cents when I want them in dollars and cents.
I've put the conversion in my AngularJS controller so when I get the data from the server I convert it from cents to dollars & cents and vice vesa just before updating. Here is the code:
# Edit Investor
window.InvestorEditCtrl = ($scope, $routeParams, $location, Investor, Common) ->
console.log 'InvestorEditCtrl'
# Setup variable for common services(factory)
$scope.common = Common
$scope.master = {} # Initialise our main object hash
investor_id = $routeParams.investor_id # Get the ID of the investor we are editing from the URL
# Get the investor information & assign it to the scope master object
console.log 'Get JSON'
$scope.investor = new Investor.show({investor_id: investor_id}, (resource) ->
# copy the response from server response (JSON format) to the scopes master
$scope.master = angular.copy(resource)
# Convert price_cents to dollars
$scope.investor.price_cents /= 100
)
# Update the investor passing the JSON back to the server.
$scope.update = (investor) ->
# Convert price_cents to cents
investor.price_cents *= 100
$scope.master = angular.copy(investor)
investor.$update({investor_id: investor_id}, (t) ->
$location.path('/investors/' + t.id)
)
Is there a better way to do this?

You could write either a filter or a directive that converts it to the form you want in your HTML. The filter would look something like this:
app.filter('centsToDollars', function() {
return function(input) {
var out = input / 100;
return out;
}
});
Then, in your html, wherever you want the cents displayed dollars and cents, call it like this:
<p>{{investor.price_cents | centsToDollars}}</p>
The filter would only impact the display of the data and wouldn't modify the underlying data from being cents.
If you needed to modify the display of an input field, the better route would probably be a directive. You could do something like what's referenced here
app.directive('myCentsToDollars', function() {
return {
restrict: 'A',
require: 'ngModel',
link: function(scope, elem, attrs, ngModel) {
var toDollars = function(text) {
var text = (text || "0");
return (parseFloat(text) / 100);
}
var toCents = function(text) {
var text = (text || "0");
return (parseFloat(text) * 100);
}
ngModel.$parsers.push(toDollars);
ngModel.$formatters.push(toCents);
}
}
});
Then, in your html, do:
<input type="text" my-cents-to-dollars ng-model="investor.price_cents" />

Related

Svelte: How to bind a formatted input field to a property

First of all: Svelte is still new to me. I hope the question is not too trivial.
Within a simple component I want to use the content of a formatted input field for a calculation.
For example:
In the input field a Euro amount should be displayed formatted (1.000).
Next to it a text with the amount plus VAT should be displayed (1.190).
How I do this without formatting is clear to me. The example looks like this:
export let net;
export let vat;
$: gross = net + (net * vat / 100);
$: grossPretty = gross.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
with a simple markup like this:
<form>
<label>Net amount</label>
<input type="text" step="any" bind:value={net} placeholder="Net amount">
</form>
<div>
Gros = {grossPretty} €
</div>
In vue i used a computed property. Its getter delivers the formatted string and its setter takes the formatted string and saves the raw value.
(In data() I define net, in the computed properties i define netInput. The input field uses netInput as v-model).
It looks like this:
netInput: {
get(){
return this.net.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
},
set(s){
s = s.replace(/[\D\s._-]+/g, "");
this.net = Number(s);
}
}
How can I handle it in svelte?
You can do something somewhat similar, you create another computed variable that stores the deformatted string from the input field and is used in the calculation instead of the direct input
export let net;
export let vat;
$: net_plain = Number(net.replace(/[\D\s._-]+/g, ""));
$: gross = net_plain + (net_plain * vat / 100);
$: grossPretty = gross.toLocaleString('de-DE',{ minimumFractionDigits: 0, maximumFractionDigits: 0 });
But maybe find a better name for the variable :)
Thanks to Stephane Vanraes I found a solution.
It has not the charm of the vue approach but it's ok. First I inserted 'net_plain'. To have the input field formatted during input, I added an event listener for the keyup event.
<input type="text" step="any" bind:value={net} on:keyup={handleKeyUp} placeholder="Net amount">
The event is handled from the function handleKeyUp as follows:
function handleKeyUp(event){
if ( window.getSelection().toString() !== '' ) {
return;
}
// ignore arrow keys
let arrows = [38,40,37,39];
if ( arrows.includes( event.keyCode)) {
return;
}
let input = event.target.value.replace(/[\D\s._-]+/g, "");
input = input ? parseInt( input, 10 ) : 0;
event.target.value = ( input === 0 ) ? "" : input.toLocaleString( "de-DE" );
}
BUT: If anyone has a solution using getter and setter I would appreciate the anwer!

How to extract integer after "=" sign using ruby

I'm trying to extract the integers after mrp= and talktime=.
var i=0;
var recharge=[];
var recharge_text=[];
var recharge_String="";
var mrp="";
var talktime="";
var validity="";
var mode="";mrp='1100';
talktime='1200.00';
validity='NA';
mode='E-Recharge';
if(typeof String.prototype.trim !== 'function') {
String.prototype.trim = function() {
return this.replace(/^ +| +$/g, '');
}
}
mrp=mrp.trim();
if(isNaN(mrp))
{
recharge_text.push({MRP:mrp, Talktime:talktime, Validity:validity ,Mode:mode});
}
else
{
mrp=parseInt(mrp);
recharge.push({MRP:mrp, Talktime:talktime, Validity:validity ,Mode:mode});
}
mrp='2200';
talktime='2400.00';
I've extracted the above text from a webpage, but I do not know how to extract that particular part alone.
You can use regular expressions to parse strings and extract parts of it :
my_text = "blablabla" #just imagine that this is your text
regex_mrp = /mrp='(.+?)';/ #extracts whatever is between single quotes after mrp
regex_talktime = /talktime='(.+?)';/ #extracts whatever is between single quotes after talktime
mrp = my_text.match(regex_mrp)[1].to_i #gets the match, and converts to integer
talktime = my_text.match(regex_talktime)[1].to_f #gets the match, and converts to float
Here's a quick reference to the regular expressions syntax : https://msdn.microsoft.com/en-us/library/az24scfc(v=vs.110).aspx
I'd do something like this:
string = <<EOT
var i=0;
var recharge=[];
var recharge_text=[];
var recharge_String="";
var mrp="";
var talktime="";
var validity="";
var mode="";mrp='1100';
talktime='1200.00';
validity='NA';
mode='E-Recharge';
if(typeof String.prototype.trim !== 'function') {
String.prototype.trim = function() {
return this.replace(/^ +| +$/g, '');
}
}
mrp=mrp.trim();
if(isNaN(mrp))
{
recharge_text.push({MRP:mrp, Talktime:talktime, Validity:validity ,Mode:mode});
}
else
{
mrp=parseInt(mrp);
recharge.push({MRP:mrp, Talktime:talktime, Validity:validity ,Mode:mode});
}
mrp='2200';
talktime='2400.00';
EOT
hits = string.scan(/(?:mrp|talktime)='[\d.]+'/)
# => ["mrp='1100'", "talktime='1200.00'", "mrp='2200'", "talktime='2400.00'"]
This gives us an array of hits using scan, where the pattern /(?:mrp|talktime)='[\d.]+'/ matched in the string. Figuring out how the pattern works is left as an exercise for the user, but Ruby's Regexp documentation explains it all.
Cleaning that up to be a bit more useful:
hash = hits.map{ |s|
str, val = s.split('=')
[str, val.delete("'")]
}.each_with_object(Hash.new { |h, k| h[k] = [] }){ |(str, val), h| h[str] << val }
You also need to read about each_with_object and what's happening with Hash.new as those are important concepts to learn in Ruby.
At this point, hash is a hash of arrays:
hash # => {"mrp"=>["1100", "2200"], "talktime"=>["1200.00", "2400.00"]}
You can easily extract a particular variable's values, and can correlate them if need be.
what if i get a string instead of integer next to "=" sign?
...
string.scan(/(?:tariff)='[\p{Print}]+'/)
It's important to understand what the pattern is doing. The regular expression engine has some gotchas that can drastically affect the speed of a search, so indiscriminately throwing in things without understanding what they do can be very costly.
When using (?:...), you're creating a non-capturing group. When you only have one item you're matching it's not necessary, nor is it particularly desirable since it's making the engine do more work. The only time I'd do that is when I need to refer back to what the capture was, but since you have only one possible thing it'll match that becomes a moot-point. So, your pattern should be reduced to:
/tariff='[\p{Print}]+'/
Which, when used, results in:
%(tariff='abcdef abc a').scan(/tariff='[\p{Print}]+'/)
# => ["tariff='abcdef abc a'"]
If you want to capture all non-empty occurrences of the string being assigned, it's easier than what you're doing. I'd use something like:
%(tariff='abcdef abc a').scan(/tariff='.+'/)
# => ["tariff='abcdef abc a'"]
%(tariff='abcdef abc a').scan(/tariff='[^']+'/)
# => ["tariff='abcdef abc a'"]
The second is more rigorous, and possible safer as it won't be tricked by an line that has multiple single-quotes:
%(tariff='abcdef abc a', 'foo').scan(/tariff='.+'/)
# => ["tariff='abcdef abc a', 'foo'"]
%(tariff='abcdef abc a', 'foo').scan(/tariff='[^']+'/)
# => ["tariff='abcdef abc a'"]
Why that works is for you to figure out.

Select2 multiple values doubles prepopulated values on init, and still sends

There are a lot of questions about select2 doubling values, and many of them don't have accepted answers.
On the surface everything looks fine but when I delete a token it's still sending it in params.
Checking the values of the input (which select2 is hiding)
Prior to initializing select2
$('#language_list').val() //=> "english spanish italian"
After init
$('#language_list').val() //=> "english spanish italian,english,spanish,italian"
// It's clearer what's going on like this.
// And I don't know if it's significant but tokenSeparators: [",", " "]
$("#user_language_list").select2("val") //=> ["english spanish italian", "english", "spanish", "italian"]
Lots of issues are coming up like when the form repopulates after an error I'll have
$('#language_list').val() //=> "english-spanish-italian english spanish italian,english,spanish,italian"
Hidden in my input which I have to address on the backend.
What worked was cleaning the value before sending your data to the callback
You'll probably recognize this as basically the code from the docs.
initSelection: function (e, callback) {
var tags = e.val().split(/, |,| /);
for (var i = 0; i < tags.length; i++) {
var tag = tags[i].trim();
tags[i] = {id: tag, text: tag};
}
callback(tags);
}
But you just need to clean the val
initSelection: function (e, callback) {
var tags = e.val().split(/, |,| /);
e.val("")
for (var i = 0; i < tags.length; i++) {
var tag = tags[i].trim();
tags[i] = {id: tag, text: tag};
}
callback(tags);
}

Angular pagination not updating when bound list changes due to filtering on an input text box

Here's the scenario:
I am using an ASP.NET MVC site with Angular JS and Boostrap UI. I have a dynamic ul list populated by data fed through a controller call to AngularJS, filtering on that list through an input search box. The list is also controlled through pagination (UI Bootstrap control) that I've setup to show 10 results per page for the list of 100 or so items. This list is filtered as the user types in the search box, however I would like the pagination to update as well so consider the following example:
The list has 10 pages of items (100 items), the user types some text in the input search box which filters the list down to 20 or so items, so the pagination should be updated from 10 pages to two pages.
I figure there must be a $watch setup somewhere, perhaps on the list items after it has been filtered and then update the pagination page count, however I'm pretty new to AngularJS so can someone please explain to me how this could be done?
Thanks very much. I have posted my code below:
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in destinations | filter: cityName |
startFrom:currentPage*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage"
max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
var destApp = angular.module('dealsPage', ['ui.bootstrap', 'uiSlider']);
destApp.controller('DestinationController', function ($scope, $http) {
$scope.destinations = {};
$scope.currentPage = 1;
$scope.pageSize = 10;
$http.get('/Deals/GetDeals').success(function (data) {
$scope.destinations = data;
$scope.noOfPages = data.length / 10;
$scope.maxSize = 5;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
Because your pagination is a combination of chained filters, Angular has no idea that when cityName changes, it should reset currentPage to 1. You'll need to handle that yourself with your own $watch.
You'll also want to adjust your startFrom filter to say (currentPage - 1) * pageSize, otherwise, you always start at page 2.
Once you get that going, you'll notice that your pagination is not accurate, because it's still based on destination.length, and not the filtered sub-set of destinations. For that, you're going to need to move your filtering logic from your view to your controller like so:
http://jsfiddle.net/jNYfd/
HTML
<div data-ng-app="dealsPage">
<input type="text" data-ng-model="cityName" />
<div data-ng-controller="DestinationController">
<ul>
<li data-ng-repeat="deals in filteredDestinations | startFrom:(currentPage - 1)*pageSize | limitTo:pageSize">{{deals.Location}}</li>
</ul>
<br />
<pagination rotate="true" num-pages="noOfPages" current-page="currentPage" max-size="maxSize" class="pagination-small" boundary-links="true"></pagination>
</div>
JavaScript
var destApp = angular.module('dealsPage', ['ui.bootstrap']);
destApp.controller('DestinationController', function ($scope, $http, $filter) {
$scope.destinations = [];
$scope.filteredDestinations = [];
for (var i = 0; i < 1000; i += 1) {
$scope.destinations.push({
Location: 'city ' + (i + 1)
});
}
$scope.pageSize = 10;
$scope.maxSize = 5;
$scope.$watch('cityName', function (newCityName) {
$scope.currentPage = 1;
$scope.filteredDestinations = $filter('filter')($scope.destinations, $scope.cityName);
$scope.noOfPages = $scope.filteredDestinations.length / 10;
});
});
destApp.filter('startFrom', function () {
return function (input, start) {
start = +start; //parse to int
return input.slice(start);
};
});
The version shared on jsfiddle is compatible with ui-bootstrap 0.5.0 but from 0.6.0 onwards there have been breaking changes.
Here is a version that uses the following libraries:
angular 1.2.11
angular-ui-bootstrap 0.10.0
bootstrap 3.1.0
Here is a plunker for the same:
Angular UI Bootstrap Pagination
Hello I tried to hook this up with Firebase using Angular Fire and it only works after I type something in the search input. In the $scope.$watch method, I used Angular Fire's orderByPriorityFilter to convert the object to an array.
$scope.$watch('search', function(oldTerm, newTerm) {
$scope.page = 1;
// Use orderByPriorityFilter to convert Firebase Object into Array
$scope.filtered = filterFilter(orderByPriorityFilter($scope.contacts), $scope.search);
$scope.lastSearch.search = oldTerm;
$scope.contactsCount = $scope.filtered.length;
});
Initial load doesn't load any contacts. It's only after I start typing in the input search field.

Open Source Projects for i18n à la Facebook

Facebook has this unique and clever approach to localization of their site: translators (in their case users that help to translate the site voluntarily) can simply click on the not-yet-translated strings – which are marked with a green bottom border – in their natural context on the site. See http://www.facebook.com/translations/.
Now, if you ever had to deal with the translation of a website, you'll be well aware of how odd and funny some of these translations can be when using tools like poedit where the translator isn't fully aware of the spot the translated string will lated appear in on the website.
Example: Please translate "Home". In German, for instance, the start page of a website would be "Home" while the house you live in is "Heim". Now, you as the translator basically have to guess which context this term is likely to appear in on the website and translate accordingly. Chances are, you're new website on home furniture now translates as "Home-Einrichtung" which sounds ridiculous to any German.
So, my question boils down to:
Do you know any open source PHP projects that work on something like this? I'm basically looking for a framework that allows you to put your internationalized website in "translation mode" and make strings clickable and translatable e.g. through a Javascript modal.
I'm not so much looking for a full-fledged and ready-made solution, but would love to know about similar projects that I can contribute code to.
Thanks in advance!
If you want to roll your own with jquery & jquery browserLanguage, this might get you going.
Tag all translatable text's contain elements with class="i18n", and include jquery, jquery browserLanguage, and your i18n script.
1. the internationalization javascript
— this needs to accept translations via ajax from your server, like:
var i18n = {};
i18n.bank = new Array();
i18n.t = function ( text, tl=$.browserLanguage ) {
var r = false;
$.ajax({
url: "/i18n_t.php?type=request&from="+ escape(text) +"&tl="+ tl,
success: function(){ i18n.bank[text] = this; r = true; }
});
return r;
};
2. php i18n translation service
— now we need to serve up translations, and accept them
the database will look like a bunch of tables, one for each language.
// SCHEMA for each language:
CREATE TABLE `en`
(
`id` INT PRIMARY KEY AUTO INCREMENT NOT NULL,
`from` VARCHAR(500) NOT NULL,
`to` VARCHAR(500) NOT NULL
)
the php will need some connection and db manipulation.. for now this may do:
//Connect to the database
$connection = mysql_connect('host (usually localhost)', 'mysql_username' , 'mysql_password');
$selection = mysql_select_db('mysql_database', $connection);
function table_exists($tablename, $database = false) {
if(!$database) {
$res = mysql_query("SELECT DATABASE()");
$database = mysql_result($res, 0);
}
$res = mysql_query("SELECT COUNT(*) AS count FROM information_schema.tables WHERE table_schema = '$database' AND table_name = '$tablename'
");
return mysql_result($res, 0) == 1;
}
the code is simply:
<?php
// .. database stuff from above goes here ..
$type=$_GET["type"];
$from=$_GET["from"];
$to=$_GET["to"];
$tl=$_GET["tl"];
if (! table_exists($tl)) {
...
}
if ($type == "request") { // might want to set $tl="en" when ! table_exists($tl)
$find = mysql_query("SELECT to FROM `'$tl'` WHERE from='$from'");
$row = mysql_fetch_array($find);
echo $row['to'];
} elsif ($type == "suggest") {
$find = mysql_query("SELECT COUNT(*) AS count FROM `'$tl'` WHERE from='$from'");
if ( !(mysql_result($res, 0)) == 0 ) {
$ins = mysql_query("INSERT INTO `'$tl'` (from, to) VALUES ('$from','$to')");
}
}
?>
3. page translation mechanics
— finally we can tie them together in your webpages with some further jquery:
i18n.suggest = function (from) { // post user translation to our php
$.ajax({
url: "/i18n_t.php?type=suggest&from='+from+'&to="+ escape( $('#i18n_s').contents() ) +"&tl="+ $.browserLanguage,
success: function(){ $('#i18n_t_div').html('<em>Thanks!</em>').delay(334).fadeOut().remove(); }
});
};
$(document).ready(function() {
i18n.t("submit");
i18n.t("Thanks!");
$('.i18n').click( function(event) { //add an onClick event for all i18n spans
$('#i18n_t_div').remove;
$(this).parent().append(
'<div id="i18n_t_div"><form class="i18n_t_form">
<input type="text" id="i18n_s" name="suggestion" value="+$(this).contents()+" />
<input type="button" value="'+ i18n.bank[ "submit" ] +'" onclick="i18n.suggest( '+$(this).contents()+' )" />
</form></div>'
);
}).each(function(){
var c = $(this).contents(); //now load initial translations for browser language for all the internationalized content on the page
if ( i18n.t(c) ){
$(this).html(i18n.bank[c]);
}
});
});
Mind you I don't have a server to test this on... and I don't actually code php. :D It will take some debugging but the scaffolding should be correct.

Resources