a quick javascript lightbox

I had to develop some feature which required some javascript lightbox,
I really not needed a fancy one, but a small or medium one
it was for some small confirmation and in addition a couple of inputs on confirmation
so you can imagine a small html form being poped up
I had to add one then, and I guessed tomorrow there might be couple of more coming

I found very nice options when googled
but I thought of implementing one of my own just for fun, and I got this a quick one

admin.lightbox.show(
  html: "my html goes here"
  title: "my title"
).done(->
  alert "Am done"

hi there

And it really took a very less time to build this one as follows


window.admin ||= {}
$.extend window.admin,
lightbox:
show: (options) ->
options = {} unless options
buttonDone = options.buttonDone or "Done"
buttonCancel = options.buttonCancel or "Cancel"
focus = options.focus or "input"
$("body").append "<div id='modal_shadow'></div>" unless $("div#modal_shadow")[0]
$("body").append "<div id='admin-lightbox'></div>" unless $("div#admin-lightbox")[0]
$("div#admin-lightbox").html "<div id='content'>" + options.html + "</div><div id='buttons'></div>"
$("div#admin-lightbox div#buttons").append "<button onclick='admin.lightbox.do_done(); return false;'>" + buttonDone + "</button>"
$("div#admin-lightbox div#buttons").append "<button onclick='admin.lightbox.do_cancel(); return false;'>" + buttonCancel + "</button>"
$("div#admin-lightbox").prepend "<div id='title'>" + options.title + "</div>" if options.title
$("div#modal_shadow").show()
$("div#admin-lightbox").show()
$($("div#admin-lightbox " + focus)[0]).focus()
admin.lightbox
callback_done: null
done: (callback) ->
admin.lightbox.callback_done = callback
admin.lightbox
do_done: ->
try
admin.lightbox.callback_done()
admin.lightbox.hide()
false
callback_cancel: null
cancel: (callback) ->
admin.lightbox.callback_cancel = callback
admin.lightbox
do_cancel: ->
try
admin.lightbox.callback_cancel()
admin.lightbox.hide()
false
hide: ->
$("div#modal_shadow").hide()
$("div#admin-lightbox").hide()
admin.lightbox

The most interesting part here is to be able to define callbacks like done and cancel.


body.active_admin #modal_shadow{
z-index: 999998;
position:fixed;
top: 0px; left: 0px;
height:100%; width: 100%;
background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQIW2NsampKAwAFAgHu04pC4AAAAABJRU5ErkJggg==) repeat;
display:none;
padding: 0; margin: 0;
}
body.active_admin #admin-layout{
padding:10px;
margin:10px;
top: 100px;
min-height:100px;min-width:200px;
display:none;
background-color:white;
z-index:999999;
-webkit-border-radius: 5px;
-moz-border-radius: 5px;
border-radius: 5px;
box-shadow: 0px 5px 19px #777;
-o-box-shadow: 0px 5px 19px #777;
-webkit-box-shadow: 0px 5px 19px #777;
-moz-box-shadow: 0px 5px 19px #777;
position:fixed;
top: 50%;
left: 50%;
margin-left: -200px;
margin-top: -150px;
}
body.active_admin #admin-layout #title{
padding:2px 5px;
background-color: #efefef;
font-weight: bold;
color:#555;
font-size: 1.1em;
margin-bottom: 10px;
-webkit-border-top-left-radius: 5px;
-webkit-border-top-right-radius: 5px;
-moz-border-radius-topleft: 5px;
-moz-border-radius-topright: 5px;
border-top-left-radius: 5px;
border-top-right-radius: 5px;
}
body.active_admin #admin-layout #content{
float:left;
margin-bottom:10px;
}
body.active_admin #admin-layout #buttons{
height:42px;
width:96.5%;
padding:5px;
float:right;
background-color: #efefef;
float:right;
border-bottom: 1px solid #eee;
border-left: 1px solid #eee;
border-right: 1px solid #eee;
-webkit-border-bottom-right-radius: 5px;
-webkit-border-bottom-left-radius: 5px;
-moz-border-radius-bottomright: 5px;
-moz-border-radius-bottomleft: 5px;
border-bottom-right-radius: 5px;
border-bottom-left-radius: 5px;
}
body.active_admin #admin-layout #buttons button{
margin:8px;
padding:8px;
padding-top:6px;
padding-bottom:6px;
float:right;
font-weight: bold;
color:#555;
font-size: 1.0em;
}

And I welcome all suggestions :)

activeadmin cool tweaks

tweak 1: Make some column quick-editable on the index list page

1

Ok… so I want to be quickly able to edit few names, here on the same list page, something like below

2

Thats simply great!

Now it makes me quickly update any name there itself on the list page, <enter> will make it, <escape> will ignore it, and <tab> will move to the next one. awesome.

And to make it work I just need to have one line of code change

All I need to say is `editable_test_column`, and its done.


ActiveAdmin.register Article do
index do
selectable_column
id_column
column :name, :sortable => :name do |resource|
editable_text_column resource, :name
end
column :article_type
column :created_at
column :updated_at
default_actions
end
end

view raw

article.rb

hosted with ❤ by GitHub

And here how it works, you will need to define a couple of things

You need to define editable_text_column, this you could place inside app/admin/lib/


module ActiveAdmin
module Views
class IndexAsTable < ActiveAdmin::Component
def editable_text_column resource, attr
val = resource.send(attr)
val = "&nbsp;" if val.blank?
html = %{
<div id='editable_text_column_#{resource.id}'
class='editable_text_column'
ondblclick='admin.editable_text_column_do(this)' >
#{val}
</div>
<input
data-path='#{resource.class.name.tableize}'
data-attr='#{attr}'
data-resource-id='#{resource.id}'
class='editable_text_column admin-editable'
id='editable_text_column_#{resource.id}'
style='display:none;' />
}
html.html_safe
end

Then you will need to add some javascript …


var admin = {
init: function(){
admin.set_admin_editable_events();
},
set_admin_editable_events: function(){
$(".admin-editable").live("keypress", function(e){
if ( e.keyCode==27 )
$( e.currentTarget ).hide();
if ( e.keyCode==13 ){
var path = $( e.currentTarget ).attr("data-path");
var attr = $( e.currentTarget ).attr("data-attr");
var resource_id = $( e.currentTarget ).attr("data-resource-id");
var val = $( e.currentTarget ).val();
val = $.trim(val)
if (val.length==0)
val = "&nbsp;";
$("div#"+$( e.currentTarget ).attr("id")).html(val);
$( e.currentTarget ).hide();
var payload = {}
resource_class = path.slice(0,1) // e.g. path = meters, resource_class = meter
payload[resource_class] = {};
payload[resource_class][attr] = val;
$.put("/admin/"+path+"/"+resource_id, payload).done(function(result){
console.log(result);
});
}
});
$(".admin-editable").live("blur", function(e){
$( e.currentTarget ).hide();
});
},
editable_text_column_do: function(el){
var input = "input#"+$(el).attr("id")
$(input).width( $(el).width()+4 ).height( $(el).height()+4 );
$(input).css({top: ( $(el).offset().top2 ), left: ( $(el).offset().left2 ), position:'absolute'});
val = $.trim( $(el).html() );
if (val=="&nbsp;")
val = "";
$(input).val( val );
$(input).show();
$(input).focus();
}
}
$( document ).ready(function() {
admin.init();
});

view raw

active_admin.js

hosted with ❤ by GitHub

and some css stuff


input.editable_text_column {
background-color: #FBFF8C;
border: 1px none #FBFF8C;
box-shadow: 0 2px 19px #AAAAAA;
color: black;
display: none;
font-family: serif;
font-size: 1em;
font-style: italic;
height: 30px;
position: absolute;
text-align: center;
vertical-align: middle;
width: 180px;
z-index: 9090;
}
div.editable_text_column{
padding:4px;
background-color: #f9fbc2;
}

Thats it.

Next, I would love to quickly alter article type say having a dropdown there to save my time

tweak 2: quick editable dropdowns on list page

6

Ohh, now this is again great!   … no? It actually updates the article type on value change. nice.

Let see the code behind


ActiveAdmin.register Article do
index do
selectable_column
id_column
column :name, :sortable => :name do |resource|
editable_text_column resource, :name
end
column 'Type', :sortable => :article_type do |resource|
column_select(resource, :article_type, ["News", "Story", "Case Study", "Business"])
end
column :created_at
column :updated_at
default_actions
end
end

view raw

article.rb

hosted with ❤ by GitHub

and to define `column_select`


module ActiveAdmin
module Views
class IndexAsTable < ActiveAdmin::Component
include CustomHelper
def column_select resource, attr, list
val = resource.send(attr)
html = _select list, val, { "attrs" => %{
data-path='#{resource.class.name.tableize}'
data-attr='#{attr}'
data-resource-id='#{resource.id}'
class='admin-selectable'
}
}
html.html_safe
end

CustomHelper is one of my helper modules that really helps me render dropdown the way I need, and provides me some nice options, one may choose to use the default rails select helpers


module CustomHelper
def _select set, selected, options={}
style = "margin:0;padding:0;#{options["style"]}"
selected = options["default"] if selected.blank?
cache = options["cache"] || nil
id = options["id"] || nil
id ||= "ch#{ch_uniq_id}" unless cache.blank?
html = ""
html << "<select "
html << " style='#{style}' "
html << " #{options["attrs"]} "
html << " data-cache='#{cache}' " unless cache.blank?
html << " id='#{id}' " unless id.blank?
html << ">"
if options["blank"]
html << "<option></option>"
end
unless cache.blank?
options_html = Rails.cache.fetch("CustomHelper:#{cache}", expires_in: 1.minute ) { _select_options set, selected, options }
else
options_html = _select_options set, selected, options
end
html << options_html
html << "</select>"
unless cache.blank?
html << %{
<script type="text/javascript">
$( document ).ready(function() {
$("select##{id}").val("#{selected.to_s.strip}");
});
</script>
}
end
html
end
def _select_options set, selected, options
html = ""
selected = selected.to_s.strip
arr_match = options["arr_match"] || 0
set.each do |option|
check = option
value = option
if option.is_a? Array
check = option[arr_match]
value = option[0]
option = option[1]
end
if selected==check.to_s.strip
html << "<option value='#{value}' selected>#{option}</option>"
else
html << "<option value='#{value}'>#{option}</option>"
end
end
html
end
def ch_uniq_id
@ch_uniq_id ||= Time.now.to_i
@ch_uniq_id += 1
end
end

Then you need some javascript


var admin = {
init: function(){
admin.set_admin_selectable_events();
},
set_admin_selectable_events: function(){
$("select.admin-selectable").live("change", function(e){
var path = $( e.currentTarget ).attr("data-path");
var attr = $( e.currentTarget ).attr("data-attr");
var resource_id = $( e.currentTarget ).attr("data-resource-id");
var val = $( e.currentTarget ).val();
val = $.trim(val)
var payload = {}
resource_class = path.slice(0,1)
payload[resource_class] = {};
payload[resource_class][attr] = val;
$.put("/admin/"+path+"/"+resource_id, payload).done(function(result){
console.log(result);
});
});
}
}
$( document ).ready(function() {
admin.init();
});

view raw

active_admin.js

hosted with ❤ by GitHub

done.

I do have worked out several more tweaks like toggle button for boolean column, show/hide columns on the list page, auto-populate/suggest feature for string filters, one can make use ActiveAdmin icons too, then for one of my requirements I have set up the whole custom header and a dozens of custom pages. I would easily say that ActiveAdmin is just simple and awesome, one can really customize it to the extreme, the code structure will allow you to imagine and implement a number of custom scenarios.

DRYing if @current_user.nil?

While working on one of rails applications, as I usually tend to check whether user is logged in, specially in views as follows

<% if @current_user.nil? %>
  <a href='url to login page'>
<% else %>
  <a href='some url where logged in user have access'>
<% end %>

And over some period I found this pattern repeating everywhere.

I thought of DRYing it, I wanted to have a kind of before_filter for every link that appear on the page, every link should go for login if that page or feature require user entity. and this should be handled by a common thing, similar to before_filter: login_require, on server side. I intend to achieve similar in views. A while back when I read up about html5 custom data attribute, and unobtrusive javascript etc, referring to them, I got one idea as described below, though it is not something that I am making use of custom data attribute exactly, but I found this idea useful and working. May be someone would have good suggestions here for me.

I will put any link which require user login as follows

<a href="/feature/page/" login-require >Feature</a>

And then I will have my after_load.js which will have the following event binding

jQuery('a[login-require]').bind('click', function() {
  <% if @current_user.nil? %>
    openOverlay('overlay-login');
    return false;
  <% else %>
    return true;
  <% end %>
});