Backbone.js: Change datetime field from Rails - ruby-on-rails

When I print the created_at field of a model using Backbone.js( <i> {{created_at}} </i>
), I'm getting: 2011-08-07T12:03:00Z.
I'm trying to change the date to a readable format for hours and I'm not being able to.
I want the T and Z to be removed before printing.
thanks

Change the variable in your template context, or add a new one. I would add a new one.
If this is in your render function, it may be changed to something like this:
render: function() {
var context = this.model.toJSON();
context['created_at_formatted'] = this.formatDate(context['created_at']);
var html = this.template.to_html(context);
$(this.el).html(html);
}
With a function added to the view like this:
formatDate: function(d) {
var pad = function (n) {
return n < 10 ? '0' + n : n;
};
// in case the date is a string; you can remove this if you know it will be a date
if (typeof date === 'string') {
d = new Date(d);
}
return [
d.getUTCFullYear(), '-',
pad(d.getUTCMonth() + 1), '-',
pad(d.getUTCDate()), ' ',
pad(d.getUTCHours()), ':',
pad(d.getUTCMinutes()), ':',
pad(d.getUTCSeconds())
].join("");
}
Then the template would be like this:
<i> {{created_at_formatted}} </i>
To simplify the date formatting function, you could also use a date formatting library like DateJS. You could also move the date formatting to the model.

Readable? Why not jQuery timeago?
jQuery timeago
In your Backbone model,
toTemplateData: ->
_.extend
created_duration: do => $.timeago #get("created_at") if #get("created_at")
In your Backbone view,
render: ->
#$el.html(#template(#model.toTemplateData() ))
In your template,
<time title="{{created_duration}}"></time>

You can use the strftime on your created_at
<i> {{created_at.strftime('%Y/%m/%d')}} </i>

Ben's answer is correct but the formatting of the date should really be in a model somewhere as it is business logic.
My variation of that would be:
render: function() {
var context = this.model.format().toJSON();
var html = this.template.to_html(context);
$(this.el).html(html);
}
And in your model, something like:
format() {
this.set('created_at_formatted', formatDate(this.get('created_at')));
return this;
}
formatDate: function(d) {
// format date stuff (See Ben`s answer)
return d;
}
And your template would render:
<i> {{created_at}} </i>
This removes some redundancy, promotes reuse and it is easier to read. Perhaps my answer needs a little work but hopefully you get the general idea.

Related

Displaying a human readable date time in angular js that comes from rails backend

I want to show human readable date time in my frontend. My data comes from rails backend. When I use {{ item.created_at }} it shows the time like rails way 2016-10-10T10:29:47.993Z. But How can I show this like 5 days ago, 3 hours ago in angular js?
To format dates in angular you can use date filter like this:
{{ item.created_at | date:'yyyy-MM-dd HH:mm' }}
You are looking for a very particular format so I think you need to build a custom filter to show exactly that. You can use this filter scaffolding:
.filter('customDate', function() {
return function(date) {
// 1. Get current date
// 2. Get diff from expression date to current
// 3. Apply your format and return result;
};
});
Lastly there is a library called momentjs to manipulate dates and times and there is an angular version of that:
Check the am-time-ago directive of the library:
<span am-time-ago="item.created_at"></span>
Try this, Create a filter for that, for reusability of code
var jimApp = angular.module("mainApp", []);
jimApp.controller('mainCtrl', function($scope){
$scope.date = "1992-05-07T22:00:00.000Z";
});
jimApp.filter('dateFilter', function() {
function calculateDate(date) {
date = new Date(date);
var year = date.getFullYear();
var month = date.getMonth()+1;
var day = date.getDate();
return day+'-'+month+'-'+year;
}
return function(date) {
return calculateDate(date);
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="mainApp" ng-controller="mainCtrl">
<div>{{date | dateFilter}}</div>
</div>

tablesorter to sort columns which has <br> within them

I have a table which has one of the column's as datetime: eg: 1/11/2011 12:34 PM
Unfortunately, the width of the column does not allow me to display datetime in full length in one line, hence I am displaying the contents in two lines, like
1/11/2011
12:34 PM
But tablesorter will not work if the column contents have a <br> in them. Any idea how I can achieve sorting via tablesorter for this issue? I am having tablesorter revision 2.0.5b. I cannot upgrade to newer version because it might break existing features of the rails app.
tablesorter is the jquery plugin
You'll probably need a custom parser to remove the carriage return; honestly, I don't think a <br> needs to be added if the text is allowed to wrap, and you set a width for that column.
Anyway, try this code (demo)
$(function () {
$.tablesorter.addParser({
// set a unique id
id: 'date',
is: function (s, table, cell) {
// return false so this parser is not auto detected
return false;
},
format: function (s, table, cell, cellIndex) {
// replace extra spacing/carriage returns
var str = s.replace(/\s+/g," "),
date = new Date( str );
return date instanceof Date && isFinite(date) ? date.getTime() : s;
},
// set type, either numeric or text
type: 'numeric'
});
$('table').tablesorter({
theme: 'blue',
headers: {
7: { sorter: 'date' }
}
});
});

ReactJS with dynamic type objects

I am working on a project to convert some of the rails view layer to ReactJS. One of the challenge that I have is to render a list of dynamic type of objects (Objects are using STI).
for example, I am trying to render a bag of fruits, and each fruit has a specific partial views in rails. In rails, I would do
fruits.each do |fruit|
#fruit.type could be orange, apple, banana, etc.
render 'fruits/' + fruit.type
end
How do i do this in ReactJS? Is it possible?
You can just create an object.
var things = {
foo: FooComponent, ...
};
And then get a component from that.
var key = 'foo';
var Component = things[key];
return <Component />
Note that the variable must start with an uppercase letter if you're using jsx, otherwise it will assume you mean the html element <component></component>.
Or don't use jsx here.
React.creatElement(things[key], null);
Without additional information on where you need to do this, I'm going to assume you need it at render time, in which case: just do the same thing in JavaScript.
.....React.createClass({
...
getInitialState: function() {
return {
fruits: this.props.fruits || []
};
},
...
render: function() {
var fruits = this.state.fruits.map(function(f) {
return <li>{f.type}</li>;
});
return <ul>{fruits}</ul>;
},
...
});

Select2 searches inside the html of the formatted options

I am using the select2 widget, and I need to display the search results formatted as html.
So I am using it like this:
function formatMyItem(myItem) {
return defaultEscapeMarkup(myItem.someDescription) + " <strong>(" + myItem.someOtherValue + ")</strong>";
}
function defaultEscapeMarkup(markup) {
var replace_map = {
'\\': '\',
'&': '&',
'<': '<',
'>': '>',
'"': '"',
"'": ''',
"/": '/'
};
return String(markup).replace(/[&<>"'\/\\]/g, function (match) {
return replace_map[match];
});
}
var suggestionValues = [];
for (var i = 0; i < myData.length; i++) {
var myItem = myData[i];
suggestionValues.push({
id: myItem.someKey,
text: formatMyItem(myItem)
});
}
$mySelect.select2({
width: 'resolve',
multiple: true,
data: suggestionValues,
escapeMarkup: function(m) {
// Do not escape HTML in the select options text
return m;
}
});
But now when the user searches for something, that term is searched inside the HTML of the option.
For example, if the user searches for "strong" (assumming that some descriptions can contain the word "strong"), then select2 will suggest all the values (because all of them contain "strong").
Also, when the user searches for "<" (assuming that some descriptions contain mathematical symbols), then select2 will return all values (because all of them contain html tags), but will not highlight the actual "less than" symbol in the descriptions, because they have been actually converted to "& lt;".
How can I make select2 not search inside the html tags?
Ok, it seems the solution was actually quite simple :D
I added the following:
$mySelect.select2({
width: 'resolve',
multiple: true,
data: suggestionValues,
escapeMarkup: function(m) {
// Do not escape HTML in the select options text
return m;
},
matcher: function(term, text) {
// Search the term in the formatted text
return $("<div/>").html(text).text().toUpperCase().indexOf(term.toUpperCase())>=0;
}
});
So now when the user searches for "strong" they get only the relevant results.
But now there is another issue:
Now, if the user searches for "<", then select2 will highlight the "<" inside the strong tag.
It seems that I need to also "patch" somehow the search-results highlighter...
EDIT : Coming back to this, it seems that the solution for the highlighting is not so easy...
The default implementation in select2 is like this:
formatResult: function(result, container, query, escapeMarkup) {
var markup=[];
markMatch(result.text, query.term, markup, escapeMarkup);
return markup.join("");
},
.......
function markMatch(text, term, markup, escapeMarkup) {
var match=text.toUpperCase().indexOf(term.toUpperCase()),
tl=term.length;
if (match<0) {
markup.push(escapeMarkup(text));
return;
}
markup.push(escapeMarkup(text.substring(0, match)));
markup.push("<span class='select2-match'>");
markup.push(escapeMarkup(text.substring(match, match + tl)));
markup.push("</span>");
markup.push(escapeMarkup(text.substring(match + tl, text.length)));
}
Somehow I need to replace these two functions, but I cannot find an easy solution for mapping from the range of characters in the formatted HTML (the search-term to highlight) back to the source html (so that I can add the < span class='select2-match' > ) ...
If any of you has better solutions, please feel free to share them...

Convert Select2 input to tokens

Does the Select2 jQuery plug-in have a built-in function for converting strings to tokens?
I want to be able to call this tokenizing function when the user pastes strings into a Select2 field so that the pasted input becomes tokens.
I think I have solved the question myself with the following code:
// force tokenizing of Select2 auto-complete fields after pasting
$('body').on('paste', '.select2-input', function() {
// append a delimiter and trigger an update
$(this).val(this.value + ',').trigger('input');
});
This assumes that commas are set as delimiters in the plug-in's "tokenSeparators" initialization setting.
For 4.0.1 version:
$('#my-select').data('select2').dataAdapter.$search.val("tag1,tag2,").trigger("input");
This will add two tags: tag1 and tag2 (note trailing ,).
Important: you should add data: [] into select2 init parameters.
Use an input type text, and assign the select2 to it. Like
<input type="text" id="makeTokens" />
and then in javascript
$("#makeTokens").select2({
placeholder: "Paste data",
tags: ["red", "green", "blue"],
tokenSeparators: [",", " "]
});
in the tags, you can assign any values that you want it to display as select options and use the tokenSeperators to seperate the text on commas or spaces etc.
Note: The resultant input value will be comma seperated tokens.
For some reason Donald's solution didn't work for me (maybe newer versions of select2 behaves differently). This is what worked for me:
$('body').on('paste', '.select2-input', function (e) {
var pasteData = (e.originalEvent || e).clipboardData.getData('text/plain') || '';
$(this).val(pasteData + ',');
e.preventDefault();
});
Since at the point the event was triggered the value of .select2-input was an empty string, I extractacted the pasted string from the event object. Apparently the default select2 for copying action was still triggering after this, so I had to add e.preventDefault(); to stop it from running and messing up the input.
just run this jQuery which takes the separatoes from options.tokenSeparators directly, and applies for all select2 instances in the page automatically:
$(document).on('paste', 'span.select2', function (e) {
e.preventDefault();
var select = $(e.target).closest('.select2').prev();
var clipboard = (e.originalEvent || e).clipboardData.getData('text/plain');
var createOption = function (value, selected) {
selected = typeof selected !== 'undefined' ? selected : true;
return $("<option></option>")
.attr("value", value)
.attr("selected", selected)
.text(value)[0]
};
$.each(
clipboard.split(new RegExp(select.data('select2').options.options.tokenSeparators.map(function (a) {
return (a).replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}).join('|'))),
function (key, value) {
if (value && (!select.val() || (select.val() && select.val().indexOf('' + value) == -1))) {
select.append(createOption(value));
}
});
select.trigger('change');
});

Resources