diff --git a/app/assets/javascripts/rails_admin/jquery.ui.timepicker.js b/app/assets/javascripts/rails_admin/jquery.ui.timepicker.js
index 63ffa9a5a2..60a3d610f6 100644
--- a/app/assets/javascripts/rails_admin/jquery.ui.timepicker.js
+++ b/app/assets/javascripts/rails_admin/jquery.ui.timepicker.js
@@ -1,54 +1,54 @@
/*
-* jQuery UI Timepicker 0.3.1
-*
-* Copyright 2010-2011, Francois Gelinas
-* Dual licensed under the MIT or GPL Version 2 licenses.
-* http://jquery.org/license
-*
-* http://fgelinas.com/code/timepicker
-*
-* Depends:
-* jquery.ui.core.js
-* jquery.ui.position.js (only if position settngs are used)
-*
-* Change version 0.1.0 - moved the t-rex up here
-*
-____
-___ .-~. /_"-._
-`-._~-. / /_ "~o\ :Y
-\ \ / : \~x. ` ')
-] Y / | Y< ~-.__j
-/ ! _.--~T : l l< /.-~
-/ / ____.--~ . ` l /~\ \<|Y
-/ / .-~~" /| . ',-~\ \L|
-/ / / .^ \ Y~Y \.^>/l_ "--'
-/ Y .-"( . l__ j_j l_/ /~_.-~ .
-Y l / \ ) ~~~." / `/"~ / \.__/l_
-| \ _.-" ~-{__ l : l._Z~-.___.--~
-| ~---~ / ~~"---\_ ' __[>
-l . _.^ ___ _>-y~
-\ \ . .-~ .-~ ~>--" /
-\ ~---" / ./ _.-'
-"-.,_____.,_ _.--~\ _.-~
-~~ ( _} -Row
-`. ~(
-) \
-/,`--'~\--'~\
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
-->T-Rex<-
+ * jQuery UI Timepicker
+ *
+ * Copyright 2010-2013, Francois Gelinas
+ * Dual licensed under the MIT or GPL Version 2 licenses.
+ * http://jquery.org/license
+ *
+ * http://fgelinas.com/code/timepicker
+ *
+ * Depends:
+ * jquery.ui.core.js
+ * jquery.ui.position.js (only if position settngs are used)
+ *
+ * Change version 0.1.0 - moved the t-rex up here
+ *
+ ____
+ ___ .-~. /_"-._
+ `-._~-. / /_ "~o\ :Y
+ \ \ / : \~x. ` ')
+ ] Y / | Y< ~-.__j
+ / ! _.--~T : l l< /.-~
+ / / ____.--~ . ` l /~\ \<|Y
+ / / .-~~" /| . ',-~\ \L|
+ / / / .^ \ Y~Y \.^>/l_ "--'
+ / Y .-"( . l__ j_j l_/ /~_.-~ .
+ Y l / \ ) ~~~." / `/"~ / \.__/l_
+ | \ _.-" ~-{__ l : l._Z~-.___.--~
+ | ~---~ / ~~"---\_ ' __[>
+ l . _.^ ___ _>-y~
+ \ \ . .-~ .-~ ~>--" /
+ \ ~---" / ./ _.-'
+ "-.,_____.,_ _.--~\ _.-~
+ ~~ ( _} -Row
+ `. ~(
+ ) \
+ /,`--'~\--'~\
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ ->T-Rex<-
*/
(function ($) {
- $.extend($.ui, { timepicker: { version: "0.3.1"} });
+ $.extend($.ui, { timepicker: { version: "0.3.2"} });
var PROP_NAME = 'timepicker',
tpuuid = new Date().getTime();
/* Time picker manager.
-Use the singleton instance of this class, $.timepicker, to interact with the time picker.
-Settings for (groups of) time pickers are maintained in an instance object,
-allowing multiple different settings on the same page. */
+ Use the singleton instance of this class, $.timepicker, to interact with the time picker.
+ Settings for (groups of) time pickers are maintained in an instance object,
+ allowing multiple different settings on the same page. */
function Timepicker() {
this.debug = true; // Change this to true to start debugging
@@ -64,61 +64,61 @@ allowing multiple different settings on the same page. */
this.regional = []; // Available regional settings, indexed by language code
this.regional[''] = { // Default regional settings
- hourText: 'Hour', // Display text for hours section
- minuteText: 'Minute', // Display text for minutes link
- amPmText: ['AM', 'PM'], // Display text for AM PM
- closeButtonText: 'Done', // Text for the confirmation button (ok button)
- nowButtonText: 'Now', // Text for the now button
- deselectButtonText: 'Deselect' // Text for the deselect button
+ hourText: 'Hour', // Display text for hours section
+ minuteText: 'Minute', // Display text for minutes link
+ amPmText: ['AM', 'PM'], // Display text for AM PM
+ closeButtonText: 'Done', // Text for the confirmation button (ok button)
+ nowButtonText: 'Now', // Text for the now button
+ deselectButtonText: 'Deselect' // Text for the deselect button
};
this._defaults = { // Global defaults for all the time picker instances
- showOn: 'focus', // 'focus' for popup on focus,
+ showOn: 'focus', // 'focus' for popup on focus,
// 'button' for trigger button, or 'both' for either (not yet implemented)
- button: null, // 'button' element that will trigger the timepicker
- showAnim: 'fadeIn', // Name of jQuery animation for popup
- showOptions: {}, // Options for enhanced animations
- appendText: '', // Display text following the input box, e.g. showing the format
-
- beforeShow: null, // Define a callback function executed before the timepicker is shown
- onSelect: null, // Define a callback function when a hour / minutes is selected
- onClose: null, // Define a callback function when the timepicker is closed
-
- timeSeparator: ':', // The character to use to separate hours and minutes.
- periodSeparator: ' ', // The character to use to separate the time from the time period.
- showPeriod: false, // Define whether or not to show AM/PM with selected time
- showPeriodLabels: true, // Show the AM/PM labels on the left of the time picker
- showLeadingZero: true, // Define whether or not to show a leading zero for hours < 10. [true/false]
- showMinutesLeadingZero: true, // Define whether or not to show a leading zero for minutes < 10.
- altField: '', // Selector for an alternate field to store selected time into
- defaultTime: 'now', // Used as default time when input field is empty or for inline timePicker
+ button: null, // 'button' element that will trigger the timepicker
+ showAnim: 'fadeIn', // Name of jQuery animation for popup
+ showOptions: {}, // Options for enhanced animations
+ appendText: '', // Display text following the input box, e.g. showing the format
+
+ beforeShow: null, // Define a callback function executed before the timepicker is shown
+ onSelect: null, // Define a callback function when a hour / minutes is selected
+ onClose: null, // Define a callback function when the timepicker is closed
+
+ timeSeparator: ':', // The character to use to separate hours and minutes.
+ periodSeparator: ' ', // The character to use to separate the time from the time period.
+ showPeriod: false, // Define whether or not to show AM/PM with selected time
+ showPeriodLabels: true, // Show the AM/PM labels on the left of the time picker
+ showLeadingZero: true, // Define whether or not to show a leading zero for hours < 10. [true/false]
+ showMinutesLeadingZero: true, // Define whether or not to show a leading zero for minutes < 10.
+ altField: '', // Selector for an alternate field to store selected time into
+ defaultTime: 'now', // Used as default time when input field is empty or for inline timePicker
// (set to 'now' for the current time, '' for no highlighted time)
- myPosition: 'left top', // Position of the dialog relative to the input.
+ myPosition: 'left top', // Position of the dialog relative to the input.
// see the position utility for more info : http://jqueryui.com/demos/position/
- atPosition: 'left bottom', // Position of the input element to match
+ atPosition: 'left bottom', // Position of the input element to match
// Note : if the position utility is not loaded, the timepicker will attach left top to left bottom
//NEW: 2011-02-03
- onHourShow: null, // callback for enabling / disabling on selectable hours ex : function(hour) { return true; }
- onMinuteShow: null, // callback for enabling / disabling on time selection ex : function(hour,minute) { return true; }
+ onHourShow: null, // callback for enabling / disabling on selectable hours ex : function(hour) { return true; }
+ onMinuteShow: null, // callback for enabling / disabling on time selection ex : function(hour,minute) { return true; }
hours: {
- starts: 0, // first displayed hour
- ends: 23 // last displayed hour
+ starts: 0, // first displayed hour
+ ends: 23 // last displayed hour
},
minutes: {
- starts: 0, // first displayed minute
- ends: 55, // last displayed minute
- interval: 5 // interval of displayed minutes
+ starts: 0, // first displayed minute
+ ends: 55, // last displayed minute
+ interval: 5 // interval of displayed minutes
},
- rows: 4, // number of rows for the input tables, minimum 2, makes more sense if you use multiple of 2
+ rows: 4, // number of rows for the input tables, minimum 2, makes more sense if you use multiple of 2
// 2011-08-05 0.2.4
- showHours: true, // display the hours section of the dialog
- showMinutes: true, // display the minute section of the dialog
- optionalMinutes: false, // optionally parse inputs of whole hours with minutes omitted
-
+ showHours: true, // display the hours section of the dialog
+ showMinutes: true, // display the minute section of the dialog
+ optionalMinutes: false, // optionally parse inputs of whole hours with minutes omitted
+
// buttons
- showCloseButton: false, // shows an OK button to confirm the edit
- showNowButton: false, // Shows the 'now' button
- showDeselectButton: false // Shows the deselect time button
+ showCloseButton: false, // shows an OK button to confirm the edit
+ showNowButton: false, // Shows the 'now' button
+ showDeselectButton: false // Shows the deselect time button
};
$.extend(this._defaults, this.regional['']);
@@ -141,16 +141,16 @@ allowing multiple different settings on the same page. */
},
/* Override the default settings for all instances of the time picker.
-@param settings object - the new settings to use as defaults (anonymous object)
-@return the manager object */
+ @param settings object - the new settings to use as defaults (anonymous object)
+ @return the manager object */
setDefaults: function (settings) {
extendRemove(this._defaults, settings || {});
return this;
},
/* Attach the time picker to a jQuery selection.
-@param target element - the target input field or division or span
-@param settings object - the new settings to use for this time picker instance (anonymous) */
+ @param target element - the target input field or division or span
+ @param settings object - the new settings to use for this time picker instance (anonymous) */
_attachTimepicker: function (target, settings) {
// check for settings on the control itself - in namespace 'time:'
var inlineSettings = null;
@@ -192,7 +192,7 @@ allowing multiple different settings on the same page. */
id: id, input: target, // associated target
inline: inline, // is timepicker inline or not :
tpDiv: (!inline ? this.tpDiv : // presentation div
- $('
'))
+ $(''))
};
},
@@ -229,8 +229,8 @@ allowing multiple different settings on the same page. */
$.timepicker._updateSelectedValue(inst);
$.timepicker._hideTimepicker();
-return false; // don't submit the form
-break; // select the value on enter
+ return false; // don't submit the form
+ break; // select the value on enter
case 27: $.timepicker._hideTimepicker();
break; // hide on escape
default: handled = false;
@@ -315,8 +315,8 @@ break; // select the value on enter
},
/* Pop-up the time picker for a given input field.
-@param input element - the input field attached to the time picker or
-event - if triggered by focus */
+ @param input element - the input field attached to the time picker or
+ event - if triggered by focus */
_showTimepicker: function (input) {
input = input.target || input;
if (input.nodeName.toLowerCase() != 'input') { input = $('input', input.parentNode)[0]; } // find from button/image trigger
@@ -348,10 +348,6 @@ event - if triggered by focus */
isFixed |= $(this).css('position') == 'fixed';
return !isFixed;
});
- if (isFixed && $.browser.opera) { // correction for Opera when fixed and scrolled
- $.timepicker._pos[0] -= document.documentElement.scrollLeft;
- $.timepicker._pos[1] -= document.documentElement.scrollTop;
- }
var offset = { left: $.timepicker._pos[0], top: $.timepicker._pos[1] };
@@ -362,7 +358,7 @@ event - if triggered by focus */
// position with the ui position utility, if loaded
- if ( ( ! inst.inline ) && ( typeof $.ui.position == 'object' ) ) {
+ if ( ( ! inst.inline ) && ( typeof $.ui.position == 'object' ) ) {
inst.tpDiv.position({
of: inst.input,
my: $.timepicker._get( inst, 'myPosition' ),
@@ -384,7 +380,7 @@ event - if triggered by focus */
// and adjust position before showing
offset = $.timepicker._checkOffset(inst, offset, isFixed);
inst.tpDiv.css({ position: ($.timepicker._inDialog && $.blockUI ?
-'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
+ 'static' : (isFixed ? 'fixed' : 'absolute')), display: 'none',
left: offset.left + 'px', top: offset.top + 'px'
});
if ( ! inst.inline ) {
@@ -395,9 +391,9 @@ event - if triggered by focus */
$.timepicker._timepickerShowing = true;
var borders = $.timepicker._getBorders(inst.tpDiv);
inst.tpDiv.find('iframe.ui-timepicker-cover'). // IE6- only
-css({ left: -borders[0], top: -borders[1],
-width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
-});
+ css({ left: -borders[0], top: -borders[1],
+ width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
+ });
};
// Fixed the zIndex problem for real (I hope) - FG - v 0.2.9
@@ -416,24 +412,31 @@ width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
}
},
- // This is a copy of the zIndex function of UI core 1.8.??
- // Copied in the timepicker to stay backward compatible.
+ // This is an enhanced copy of the zIndex function of UI core 1.8.?? For backward compatibility.
+ // Enhancement returns maximum zindex value discovered while traversing parent elements,
+ // rather than the first zindex value found. Ensures the timepicker popup will be in front,
+ // even in funky scenarios like non-jq dialog containers with large fixed zindex values and
+ // nested zindex-influenced elements of their own.
_getZIndex: function (target) {
- var elem = $( target ), position, value;
- while ( elem.length && elem[ 0 ] !== document ) {
- position = elem.css( "position" );
- if ( position === "absolute" || position === "relative" || position === "fixed" ) {
- value = parseInt( elem.css( "zIndex" ), 10 );
- if ( !isNaN( value ) && value !== 0 ) {
- return value;
+ var elem = $(target);
+ var maxValue = 0;
+ var position, value;
+ while (elem.length && elem[0] !== document) {
+ position = elem.css("position");
+ if (position === "absolute" || position === "relative" || position === "fixed") {
+ value = parseInt(elem.css("zIndex"), 10);
+ if (!isNaN(value) && value !== 0) {
+ if (value > maxValue) { maxValue = value; }
}
}
elem = elem.parent();
}
+
+ return maxValue;
},
/* Refresh the time picker
-@param target element - The target input field or inline container element. */
+ @param target element - The target input field or inline container element. */
_refreshTimepicker: function(target) {
var inst = this._getInst(target);
if (inst) {
@@ -453,11 +456,11 @@ width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
var borders = $.timepicker._getBorders(inst.tpDiv),
self = this;
inst.tpDiv
-.find('iframe.ui-timepicker-cover') // IE6- only
-.css({ left: -borders[0], top: -borders[1],
-width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
-})
-.end()
+ .find('iframe.ui-timepicker-cover') // IE6- only
+ .css({ left: -borders[0], top: -borders[1],
+ width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
+ })
+ .end()
// after the picker html is appended bind the click & double click events (faster in IE this way
// then letting the browser interpret the inline events)
// the binding for the minute cells also exists in _updateMinuteDisplay
@@ -471,25 +474,25 @@ width: inst.tpDiv.outerWidth(), height: inst.tpDiv.outerHeight()
.bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectHours, this))
.bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectHours, this))
.end()
-.find('.ui-timepicker td a')
+ .find('.ui-timepicker td a')
.unbind()
-.bind('mouseout', function () {
-$(this).removeClass('ui-state-hover');
-if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).removeClass('ui-timepicker-prev-hover');
-if (this.className.indexOf('ui-timepicker-next') != -1) $(this).removeClass('ui-timepicker-next-hover');
-})
-.bind('mouseover', function () {
-if ( ! self._isDisabledTimepicker(inst.inline ? inst.tpDiv.parent()[0] : inst.input[0])) {
-$(this).parents('.ui-timepicker-calendar').find('a').removeClass('ui-state-hover');
-$(this).addClass('ui-state-hover');
-if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).addClass('ui-timepicker-prev-hover');
-if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-timepicker-next-hover');
-}
-})
-.end()
-.find('.' + this._dayOverClass + ' a')
-.trigger('mouseover')
-.end()
+ .bind('mouseout', function () {
+ $(this).removeClass('ui-state-hover');
+ if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).removeClass('ui-timepicker-prev-hover');
+ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).removeClass('ui-timepicker-next-hover');
+ })
+ .bind('mouseover', function () {
+ if ( ! self._isDisabledTimepicker(inst.inline ? inst.tpDiv.parent()[0] : inst.input[0])) {
+ $(this).parents('.ui-timepicker-calendar').find('a').removeClass('ui-state-hover');
+ $(this).addClass('ui-state-hover');
+ if (this.className.indexOf('ui-timepicker-prev') != -1) $(this).addClass('ui-timepicker-prev-hover');
+ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-timepicker-next-hover');
+ }
+ })
+ .end()
+ .find('.' + this._dayOverClass + ' a')
+ .trigger('mouseover')
+ .end()
.find('.ui-timepicker-now').bind("click", function(e) {
$.timepicker.selectNow(e);
}).end()
@@ -548,7 +551,7 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
pmItems++;
}
}
- hourCounter = 0;
+ hourCounter = 0;
amRows = Math.floor(amItems / hours.length * rows);
pmRows = Math.floor(pmItems / hours.length * rows);
@@ -639,11 +642,12 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
html += buttonPanel + '';
}
html += '';
+
return html;
},
/* Special function that update the minutes selection in currently visible timepicker
-* called on hour selection when onMinuteShow is defined */
+ * called on hour selection when onMinuteShow is defined */
_updateMinuteDisplay: function (inst) {
var newHtml = this._generateHTMLMinutes(inst);
inst.tpDiv.find('td.ui-timepicker-minutes').html(newHtml);
@@ -651,17 +655,17 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
// after the picker html is appended bind the click & double click events (faster in IE this way
// then letting the browser interpret the inline events)
// yes I know, duplicate code, sorry
-/* .find('.ui-timepicker-minute-cell')
-.bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this))
-.bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this));
+/* .find('.ui-timepicker-minute-cell')
+ .bind("click", { fromDoubleClick:false }, $.proxy($.timepicker.selectMinutes, this))
+ .bind("dblclick", { fromDoubleClick:true }, $.proxy($.timepicker.selectMinutes, this));
*/
},
/*
-* Generate the minutes table
-* This is separated from the _generateHTML function because is can be called separately (when hours changes)
-*/
+ * Generate the minutes table
+ * This is separated from the _generateHTML function because is can be called separately (when hours changes)
+ */
_generateHTMLMinutes: function (inst) {
var m, row, html = '',
@@ -686,8 +690,8 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
minutesPerRow = Math.round(minutes.length / rows + 0.49); // always round up
/*
-* The minutes table
-*/
+ * The minutes table
+ */
// if currently selected minute is not enabled, we have a problem and need to select a new minute.
if (onMinuteShow &&
(onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours , inst.minutes]) == false) ) {
@@ -744,7 +748,7 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
var html = "";
var enabled = true;
- var onHourShow = this._get(inst, 'onHourShow'); //custom callback
+ var onHourShow = this._get(inst, 'onHourShow'); //custom callback
if (hour == undefined) {
html = ' | ';
@@ -752,7 +756,7 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
}
if (onHourShow) {
- enabled = onHourShow.apply((inst.input ? inst.input[0] : null), [hour]);
+ enabled = onHourShow.apply((inst.input ? inst.input[0] : null), [hour]);
}
if (enabled) {
@@ -764,26 +768,26 @@ if (this.className.indexOf('ui-timepicker-next') != -1) $(this).addClass('ui-tim
'';
}
else {
- html =
- '' +
-'' +
-displayHour.toString() +
-'' +
-' | ';
+ html =
+ '' +
+ '' +
+ displayHour.toString() +
+ '' +
+ ' | ';
}
return html;
},
/* Generate the content of a "Hour" cell */
_generateHTMLMinuteCell: function (inst, minute, displayText) {
- var html = "";
+ var html = "";
var enabled = true;
- var onMinuteShow = this._get(inst, 'onMinuteShow'); //custom callback
+ var onMinuteShow = this._get(inst, 'onMinuteShow'); //custom callback
if (onMinuteShow) {
- //NEW: 2011-02-03 we should give the hour as a parameter as well!
- enabled = onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours,minute]); //trigger callback
+ //NEW: 2011-02-03 we should give the hour as a parameter as well!
+ enabled = onMinuteShow.apply((inst.input ? inst.input[0] : null), [inst.hours,minute]); //trigger callback
}
if (minute == undefined) {
@@ -792,19 +796,19 @@ displayHour.toString() +
}
if (enabled) {
-html = '' +
-'' +
-displayText +
-' | ';
+ html = '' +
+ '' +
+ displayText +
+ ' | ';
}
else {
- html = '' +
-'' +
-displayText +
-'' +
+ html = ' | ' +
+ '' +
+ displayText +
+ '' +
' | ';
}
return html;
@@ -812,7 +816,7 @@ displayText +
/* Detach a timepicker from its control.
-@param target element - the target input field or division or span */
+ @param target element - the target input field or division or span */
_destroyTimepicker: function(target) {
var $target = $(target);
var inst = $.data(target, PROP_NAME);
@@ -832,7 +836,7 @@ displayText +
},
/* Enable the date picker to a jQuery selection.
-@param target element - the target input field or division or span */
+ @param target element - the target input field or division or span */
_enableTimepicker: function(target) {
var $target = $(target),
target_id = $target.attr('id'),
@@ -861,7 +865,7 @@ displayText +
},
/* Disable the time picker to a jQuery selection.
-@param target element - the target input field or division or span */
+ @param target element - the target input field or division or span */
_disableTimepicker: function(target) {
var $target = $(target);
var inst = $.data(target, PROP_NAME);
@@ -893,8 +897,8 @@ displayText +
},
/* Is the first field in a jQuery collection disabled as a timepicker?
-@param target_id element - the target input field or division or span
-@return boolean - true if disabled, false if enabled */
+ @param target_id element - the target input field or division or span
+ @return boolean - true if disabled, false if enabled */
_isDisabledTimepicker: function (target_id) {
if ( ! target_id) { return false; }
for (var i = 0; i < this._disabledInputs.length; i++) {
@@ -918,9 +922,9 @@ displayText +
// now check if datepicker is showing outside window viewport - move to a better place if so.
offset.left -= Math.min(offset.left, (offset.left + tpWidth > viewWidth && viewWidth > tpWidth) ?
-Math.abs(offset.left + tpWidth - viewWidth) : 0);
+ Math.abs(offset.left + tpWidth - viewWidth) : 0);
offset.top -= Math.min(offset.top, (offset.top + tpHeight > viewHeight && viewHeight > tpHeight) ?
-Math.abs(tpHeight + inputHeight) : 0);
+ Math.abs(tpHeight + inputHeight) : 0);
return offset;
},
@@ -937,14 +941,14 @@ Math.abs(tpHeight + inputHeight) : 0);
},
/* Retrieve the size of left and top borders for an element.
-@param elem (jQuery object) the element of interest
-@return (number[2]) the left and top borders */
+ @param elem (jQuery object) the element of interest
+ @return (number[2]) the left and top borders */
_getBorders: function (elem) {
var convert = function (value) {
return { thin: 1, medium: 2, thick: 3}[value] || value;
};
return [parseFloat(convert(elem.css('border-left-width'))),
-parseFloat(convert(elem.css('border-top-width')))];
+ parseFloat(convert(elem.css('border-top-width')))];
},
@@ -953,15 +957,15 @@ parseFloat(convert(elem.css('border-top-width')))];
if (!$.timepicker._curInst) { return; }
var $target = $(event.target);
if ($target[0].id != $.timepicker._mainDivId &&
-$target.parents('#' + $.timepicker._mainDivId).length == 0 &&
-!$target.hasClass($.timepicker.markerClassName) &&
-!$target.hasClass($.timepicker._triggerClass) &&
-$.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
+ $target.parents('#' + $.timepicker._mainDivId).length == 0 &&
+ !$target.hasClass($.timepicker.markerClassName) &&
+ !$target.hasClass($.timepicker._triggerClass) &&
+ $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
$.timepicker._hideTimepicker();
},
/* Hide the time picker from view.
-@param input element - the input field attached to the time picker */
+ @param input element - the input field attached to the time picker */
_hideTimepicker: function (input) {
var inst = this._curInst;
if (!inst || (input && inst != $.data(input, PROP_NAME))) { return; }
@@ -977,7 +981,7 @@ $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
}
else {
inst.tpDiv[(showAnim == 'slideDown' ? 'slideUp' :
-(showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
+ (showAnim == 'fadeIn' ? 'fadeOut' : 'hide'))]((showAnim ? duration : null), postProcess);
}
if (!showAnim) { postProcess(); }
@@ -997,7 +1001,7 @@ $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
if (onClose) {
onClose.apply(
(inst.input ? inst.input[0] : null),
- [(inst.input ? inst.input.val() : ''), inst]); // trigger custom callback
+ [(inst.input ? inst.input.val() : ''), inst]); // trigger custom callback
}
}
@@ -1011,9 +1015,9 @@ $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
},
/* Retrieve the instance data for the target control.
-@param target element - the target input field or division or span
-@return object - the associated instance data
-@throws error if a jQuery problem getting data */
+ @param target element - the target input field or division or span
+ @return object - the associated instance data
+ @throws error if a jQuery problem getting data */
_getInst: function (target) {
try {
return $.data(target, PROP_NAME);
@@ -1026,7 +1030,7 @@ $.timepicker._timepickerShowing && !($.timepicker._inDialog && $.blockUI))
/* Get a setting value, defaulting if necessary. */
_get: function (inst, name) {
return inst.settings[name] !== undefined ?
-inst.settings[name] : this._defaults[name];
+ inst.settings[name] : this._defaults[name];
},
/* Parse existing time and initialise time picker. */
@@ -1057,13 +1061,13 @@ inst.settings[name] : this._defaults[name];
},
/* Update or retrieve the settings for an existing time picker.
-@param target element - the target input field or division or span
-@param name object - the new settings to update or
-string - the name of the setting to change or retrieve,
-when retrieving also 'all' for all instance settings or
-'defaults' for all global defaults
-@param value any - the new value for the setting
-(omit if above is an object or to retrieve a value) */
+ @param target element - the target input field or division or span
+ @param name object - the new settings to update or
+ string - the name of the setting to change or retrieve,
+ when retrieving also 'all' for all instance settings or
+ 'defaults' for all global defaults
+ @param value any - the new value for the setting
+ (omit if above is an object or to retrieve a value) */
_optionTimepicker: function(target, name, value) {
var inst = this._getInst(target);
if (arguments.length == 2 && typeof name == 'string') {
@@ -1087,16 +1091,16 @@ when retrieving also 'all' for all instance settings or
/* Set the time for a jQuery selection.
-@param target element - the target input field or division or span
-@param time String - the new time */
-_setTimeTimepicker: function(target, time) {
-var inst = this._getInst(target);
-if (inst) {
-this._setTime(inst, time);
- this._updateTimepicker(inst);
-this._updateAlternate(inst, time);
-}
-},
+ @param target element - the target input field or division or span
+ @param time String - the new time */
+ _setTimeTimepicker: function(target, time) {
+ var inst = this._getInst(target);
+ if (inst) {
+ this._setTime(inst, time);
+ this._updateTimepicker(inst);
+ this._updateAlternate(inst, time);
+ }
+ },
/* Set the time directly. */
_setTime: function(inst, time, noChange) {
@@ -1129,8 +1133,8 @@ this._updateAlternate(inst, time);
},
/*
-* Parse a time string into hours and minutes
-*/
+ * Parse a time string into hours and minutes
+ */
parseTime: function (inst, timeVal) {
var retVal = new Object();
retVal.hours = -1;
@@ -1218,7 +1222,7 @@ this._updateAlternate(inst, time);
// added for onMinuteShow callback
var onMinuteShow = this._get(inst, 'onMinuteShow');
if (onMinuteShow) {
- // this will trigger a callback on selected hour to make sure selected minute is allowed.
+ // this will trigger a callback on selected hour to make sure selected minute is allowed.
this._updateMinuteDisplay(inst);
}
@@ -1301,7 +1305,7 @@ this._updateAlternate(inst, time);
if (displayHours == -1) { displayHours = 0 }
if (selectedMinutes == -1) { selectedMinutes = 0 }
- if (showPeriod) {
+ if (showPeriod) {
if (inst.hours == 0) {
displayHours = 12;
}
@@ -1383,15 +1387,15 @@ this._updateAlternate(inst, time);
/* Invoke the timepicker functionality.
-@param options string - a command, optionally followed by additional parameters or
-Object - settings for attaching new timepicker functionality
-@return jQuery object */
+ @param options string - a command, optionally followed by additional parameters or
+ Object - settings for attaching new timepicker functionality
+ @return jQuery object */
$.fn.timepicker = function (options) {
/* Initialise the time picker. */
if (!$.timepicker.initialized) {
$(document).mousedown($.timepicker._checkExternalClick).
-find('body').append($.timepicker.tpDiv);
+ find('body').append($.timepicker.tpDiv);
$.timepicker.initialized = true;
}
@@ -1400,15 +1404,15 @@ find('body').append($.timepicker.tpDiv);
var otherArgs = Array.prototype.slice.call(arguments, 1);
if (typeof options == 'string' && (options == 'getTime' || options == 'getTimeAsDate' || options == 'getHour' || options == 'getMinute' ))
return $.timepicker['_' + options + 'Timepicker'].
-apply($.timepicker, [this[0]].concat(otherArgs));
+ apply($.timepicker, [this[0]].concat(otherArgs));
if (options == 'option' && arguments.length == 2 && typeof arguments[1] == 'string')
return $.timepicker['_' + options + 'Timepicker'].
apply($.timepicker, [this[0]].concat(otherArgs));
return this.each(function () {
typeof options == 'string' ?
-$.timepicker['_' + options + 'Timepicker'].
-apply($.timepicker, [this].concat(otherArgs)) :
-$.timepicker._attachTimepicker(this, options);
+ $.timepicker['_' + options + 'Timepicker'].
+ apply($.timepicker, [this].concat(otherArgs)) :
+ $.timepicker._attachTimepicker(this, options);
});
};
@@ -1424,7 +1428,7 @@ $.timepicker._attachTimepicker(this, options);
$.timepicker = new Timepicker(); // singleton instance
$.timepicker.initialized = false;
$.timepicker.uuid = new Date().getTime();
- $.timepicker.version = "0.3.1";
+ $.timepicker.version = "0.3.2";
// Workaround for #4055
// Add another global to avoid noConflict issues with inline event handlers
diff --git a/app/assets/javascripts/rails_admin/ui.coffee b/app/assets/javascripts/rails_admin/ui.coffee
index f1e232dd44..991468c508 100644
--- a/app/assets/javascripts/rails_admin/ui.coffee
+++ b/app/assets/javascripts/rails_admin/ui.coffee
@@ -68,3 +68,10 @@ $(document).on 'rails_admin.dom_ready', ->
$(this).siblings('.control-group').hide()
$(".table").tooltip selector: "th[rel=tooltip]"
+
+$(document).on 'click', '#fields_to_export label input#check_all', () ->
+ elems = $('#fields_to_export label input')
+ if $('#fields_to_export label input#check_all').is ':checked'
+ $(elems).prop('checked', true)
+ else
+ $(elems).prop('checked',false)
diff --git a/app/assets/stylesheets/rails_admin/base/theming.scss b/app/assets/stylesheets/rails_admin/base/theming.scss
index 014409179b..4458d48a72 100644
--- a/app/assets/stylesheets/rails_admin/base/theming.scss
+++ b/app/assets/stylesheets/rails_admin/base/theming.scss
@@ -47,7 +47,8 @@ body.rails_admin {
.controls .nav {
margin-bottom:5px;
}
- .remove_nested_fields {
+ .remove_nested_fields,
+ .remove_nested_one_fields {
position:absolute;
}
margin:0px;
diff --git a/app/controllers/rails_admin/main_controller.rb b/app/controllers/rails_admin/main_controller.rb
index 1ceafb171f..cbce110bed 100644
--- a/app/controllers/rails_admin/main_controller.rb
+++ b/app/controllers/rails_admin/main_controller.rb
@@ -62,7 +62,7 @@ def get_sort_hash(model_config)
"#{abstract_model.table_name}.#{params[:sort]}"
elsif field.sortable == false # use default sort, asked field is not sortable
"#{abstract_model.table_name}.#{model_config.list.sort_by}"
- elsif field.sortable.is_a?(String) && field.sortable.include?('.') # just provide sortable, don't do anything smart
+ elsif (field.sortable.is_a?(String) || field.sortable.is_a?(Symbol)) && field.sortable.to_s.include?('.') # just provide sortable, don't do anything smart
field.sortable
elsif field.sortable.is_a?(Hash) # just join sortable hash, don't do anything smart
"#{field.sortable.keys.first}.#{field.sortable.values.first}"
diff --git a/app/helpers/rails_admin/application_helper.rb b/app/helpers/rails_admin/application_helper.rb
index c9c751de88..d7343028ad 100644
--- a/app/helpers/rails_admin/application_helper.rb
+++ b/app/helpers/rails_admin/application_helper.rb
@@ -79,9 +79,10 @@ def navigation nodes_stack, nodes, level=0
model_param = node.abstract_model.to_param
url = url_for(:action => :index, :controller => 'rails_admin/main', :model_name => model_param)
level_class = " nav-level-#{level}" if level > 0
+ nav_icon = node.navigation_icon ? %{}.html_safe : ''
li = content_tag :li, "data-model"=>model_param do
- link_to node.label_plural, url, :class => "pjax#{level_class}"
+ link_to nav_icon + node.label_plural, url, :class => "pjax#{level_class}"
end
li + navigation(nodes_stack, nodes_stack.select{ |n| n.parent.to_s == node.abstract_model.model_name}, level+1)
end.join.html_safe
diff --git a/app/views/rails_admin/main/_form_filtering_multiselect.html.haml b/app/views/rails_admin/main/_form_filtering_multiselect.html.haml
index 8244c0c992..cd0acfb6e5 100644
--- a/app/views/rails_admin/main/_form_filtering_multiselect.html.haml
+++ b/app/views/rails_admin/main/_form_filtering_multiselect.html.haml
@@ -41,7 +41,7 @@
- selected_ids = (hdv = field.html_default_value).nil? ? selected_ids : hdv
= form.select field.method_name, collection, { :selected => selected_ids, :object => form.object }, field.html_attributes.reverse_merge({:data => { :filteringmultiselect => true, :options => js_data.to_json }, :multiple => true})
-- if authorized?(:new, config.abstract_model) && !field.parent_readonly
+- if authorized?(:new, config.abstract_model) && !field.parent_readonly && field.inline_add
- path_hash = { :model_name => config.abstract_model.to_param, :modal => true }
- path_hash.merge!({ :associations => { field.inverse_of => (form.object.persisted? ? form.object.id : 'new') } }) if field.inverse_of
= link_to " ".html_safe + wording_for(:link, :new, config.abstract_model), '#', :data => { :link => new_path(path_hash) }, :class => "create btn btn-info", :style => 'margin-left:10px'
diff --git a/app/views/rails_admin/main/_form_filtering_select.html.haml b/app/views/rails_admin/main/_form_filtering_select.html.haml
index ac619cfd90..3a93ff0708 100644
--- a/app/views/rails_admin/main/_form_filtering_select.html.haml
+++ b/app/views/rails_admin/main/_form_filtering_select.html.haml
@@ -28,10 +28,10 @@
- selected_id = (hdv = field.html_default_value).nil? ? selected_id : hdv
= form.select field.method_name, collection, { :selected => selected_id, :include_blank => true }, field.html_attributes.reverse_merge({ :data => { :filteringselect => true, :options => js_data.to_json }, :placeholder => t('admin.misc.search') })
-- if authorized? :new, config.abstract_model
+- if authorized?(:new, config.abstract_model) && field.inline_add
- path_hash = { :model_name => config.abstract_model.to_param, :modal => true }
- path_hash.merge!({ :associations => { field.inverse_of => (form.object.persisted? ? form.object.id : 'new') } }) if field.inverse_of
= link_to " ".html_safe + wording_for(:link, :new, config.abstract_model), '#', :data => { :link => new_path(path_hash) }, :class => "btn btn-info create", :style => 'margin-left:10px'
-- if edit_url.present?
+- if edit_url.present? && field.inline_edit
= link_to " ".html_safe + wording_for(:link, :edit, config.abstract_model), '#', :data => { :link => edit_url }, :class => "btn btn-info update #{field.value.nil? && 'disabled'}", :style => 'margin-left:10px'
diff --git a/app/views/rails_admin/main/_form_nested_many.html.haml b/app/views/rails_admin/main/_form_nested_many.html.haml
index c70b42272d..b8f7d41aa3 100644
--- a/app/views/rails_admin/main/_form_nested_many.html.haml
+++ b/app/views/rails_admin/main/_form_nested_many.html.haml
@@ -2,7 +2,7 @@
.btn-group
%a.btn.btn-info.toggler{:'data-toggle' => "button", :'data-target' => "#{form.jquery_namespace(field)} > .tab-content, #{form.jquery_namespace(field)} > .controls > .nav", :class => (field.active? ? 'active' : '')}
%i.icon-white
- - unless field.nested_form[:update_only]
+ - unless field.nested_form[:update_only] || !field.inline_add
= form.link_to_add " #{wording_for(:link, :new, field.associated_model_config.abstract_model)}".html_safe, field.name, { :class => 'btn btn-info' }
= form.errors_for(field)
= form.help_for(field)
diff --git a/app/views/rails_admin/main/export.html.haml b/app/views/rails_admin/main/export.html.haml
index 5517b42387..a2fa5f0eaf 100644
--- a/app/views/rails_admin/main/export.html.haml
+++ b/app/views/rails_admin/main/export.html.haml
@@ -4,7 +4,12 @@
= form_tag export_path(params.merge(:all => true)), :method => 'post', :class => 'form-horizontal denser' do
%input{:name => "send_data", :type => "hidden", :value => "true"}/
- %fieldset
+ %fieldset{:id => 'fields_to_export'}
+ %div.control-group
+ %div.controls
+ %label.checkbox{:for => 'check_all'}
+ = 'Select All Fields'
+ = check_box_tag 'all', 'all', true, { :id => 'check_all' }
%legend
%i.icon-chevron-down
= t('admin.export.select')
diff --git a/lib/rails_admin/adapters/active_record.rb b/lib/rails_admin/adapters/active_record.rb
index 1bfb79123a..462602efa0 100644
--- a/lib/rails_admin/adapters/active_record.rb
+++ b/lib/rails_admin/adapters/active_record.rb
@@ -107,6 +107,10 @@ def embedded?
false
end
+ def cyclic?
+ false
+ end
+
def adapter_supports_joins?
true
end
diff --git a/lib/rails_admin/adapters/mongoid.rb b/lib/rails_admin/adapters/mongoid.rb
index 317a62804b..71a07071a5 100644
--- a/lib/rails_admin/adapters/mongoid.rb
+++ b/lib/rails_admin/adapters/mongoid.rb
@@ -6,7 +6,7 @@ module RailsAdmin
module Adapters
module Mongoid
STRING_TYPE_COLUMN_NAMES = [:name, :title, :subject]
- DISABLED_COLUMN_TYPES = ['Range']
+ DISABLED_COLUMN_TYPES = ['Range', 'Moped::BSON::Binary']
ObjectId = (::Mongoid::VERSION >= '3' ? ::Moped::BSON::ObjectId : ::BSON::ObjectId)
def new(params = {})
@@ -105,6 +105,10 @@ def embedded?
@embedded ||= !!model.associations.values.find{|a| a.macro.to_sym == :embedded_in }
end
+ def cyclic?
+ @cyclic ||= !!model.cyclic?
+ end
+
def object_id_from_string(str)
ObjectId.from_string(str)
end
@@ -327,7 +331,7 @@ def association_foreign_inverse_of_lookup(association)
def association_nested_attributes_options_lookup(association)
nested = model.nested_attributes_options.try { |o| o[association.name.to_sym] }
- if !nested && [:embeds_one, :embeds_many].include?(association.macro.to_sym)
+ if !nested && [:embeds_one, :embeds_many].include?(association.macro.to_sym) && !association.cyclic
raise <<-MSG.gsub(/^\s+/, '')
Embbeded association without accepts_nested_attributes_for can't be handled by RailsAdmin,
because embedded model doesn't have top-level access.
@@ -426,10 +430,14 @@ def perform_search_on_associated_collection(field_name, conditions)
def sort_by(options, scope)
return scope unless options[:sort]
- field_name, collection_name = options[:sort].to_s.split('.').reverse
- if collection_name && collection_name != table_name
- # sorting by associated model column is not supported, so just ignore
- return scope
+ case options[:sort]
+ when String
+ field_name, collection_name = options[:sort].split('.').reverse
+ if collection_name && collection_name != table_name
+ raise "sorting by associated model column is not supported in Non-Relational databases"
+ end
+ when Symbol
+ field_name = options[:sort].to_s
end
if options[:sort_reverse]
scope.asc field_name
diff --git a/lib/rails_admin/config.rb b/lib/rails_admin/config.rb
index d52032ddae..17d690e3b5 100644
--- a/lib/rails_admin/config.rb
+++ b/lib/rails_admin/config.rb
@@ -315,7 +315,7 @@ def reset_model(model)
# @see RailsAdmin::Config::Hideable
def visible_models(bindings)
- models.map{|m| m.with(bindings) }.select{|m| m.visible? && bindings[:controller].authorized?(:index, m.abstract_model) && !m.abstract_model.embedded?}.sort do |a, b|
+ models.map{|m| m.with(bindings) }.select{|m| m.visible? && bindings[:controller].authorized?(:index, m.abstract_model) && (!m.abstract_model.embedded? || m.abstract_model.cyclic?)}.sort do |a, b|
(weight_order = a.weight <=> b.weight) == 0 ? a.label.downcase <=> b.label.downcase : weight_order
end
end
diff --git a/lib/rails_admin/config/fields/base.rb b/lib/rails_admin/config/fields/base.rb
index 3c5854165e..24e7b5c782 100644
--- a/lib/rails_admin/config/fields/base.rb
+++ b/lib/rails_admin/config/fields/base.rb
@@ -215,10 +215,28 @@ def virtual?
def editable?
return false if @properties && @properties[:read_only]
- active_model_attr_accessible = !bindings[:object].class.active_authorizer[bindings[:view].controller.send(:_attr_accessible_role)].deny?(self.method_name)
+ role = bindings[:view].controller.send(:_attr_accessible_role)
+ active_model_attr_accessible = !bindings[:object].class.active_authorizer[role].deny?(self.method_name)
+
return true if active_model_attr_accessible
if RailsAdmin::Config.yell_for_non_accessible_fields
- Rails.logger.debug "\n\n[RailsAdmin] Please add 'attr_accessible :#{self.method_name}' in your '#{bindings[:object].class}' model definition if you want to make it editable.\nYou can also explicitely mark this field as read-only: \n\nconfig.model #{bindings[:object].class} do\n field :#{self.name} do\n read_only true\n end\nend\n\nAdd 'config.yell_for_non_accessible_fields = false' in your 'rails_admin.rb' initializer if you do not want to see these warnings\n\n"
+ accessible = "attr_accessible :#{self.method_name}#{role == :default ? '' : ", :as => :#{role}"}"
+
+ Rails.logger.debug <<-MESSAGE.strip_heredoc
+
+
+ [RailsAdmin] Please add '#{accessible}' in your '#{bindings[:object].class}' model definition if you want to make it editable.
+ You can also explicitely mark this field as read-only:
+
+ config.model #{bindings[:object].class} do
+ field :#{self.name} do
+ read_only true
+ end
+ end
+
+ Add 'config.yell_for_non_accessible_fields = false' in your 'rails_admin.rb' initializer if you do not want to see these warnings
+
+ MESSAGE
end
false
end
diff --git a/lib/rails_admin/config/fields/types/belongs_to_association.rb b/lib/rails_admin/config/fields/types/belongs_to_association.rb
index 300b2773a2..e7ba09807b 100644
--- a/lib/rails_admin/config/fields/types/belongs_to_association.rb
+++ b/lib/rails_admin/config/fields/types/belongs_to_association.rb
@@ -23,6 +23,14 @@ class BelongsToAssociation < RailsAdmin::Config::Fields::Association
nested_form ? :form_nested_one : :form_filtering_select
end
+ register_instance_option :inline_add do
+ true
+ end
+
+ register_instance_option :inline_edit do
+ true
+ end
+
def selected_id
bindings[:object].send(foreign_key)
end
diff --git a/lib/rails_admin/config/fields/types/has_many_association.rb b/lib/rails_admin/config/fields/types/has_many_association.rb
index fe0d0ec680..577add3e5c 100644
--- a/lib/rails_admin/config/fields/types/has_many_association.rb
+++ b/lib/rails_admin/config/fields/types/has_many_association.rb
@@ -22,6 +22,10 @@ class HasManyAssociation < RailsAdmin::Config::Fields::Association
self.associated_model_config.excluded?
end
+ register_instance_option :inline_add do
+ true
+ end
+
def method_name
nested_form ? "#{super}_attributes".to_sym : "#{super.to_s.singularize}_ids".to_sym # name_ids
end
diff --git a/lib/rails_admin/config/fields/types/has_one_association.rb b/lib/rails_admin/config/fields/types/has_one_association.rb
index dacde0aa4d..1df74bdf2b 100644
--- a/lib/rails_admin/config/fields/types/has_one_association.rb
+++ b/lib/rails_admin/config/fields/types/has_one_association.rb
@@ -17,6 +17,15 @@ class HasOneAssociation < RailsAdmin::Config::Fields::Association
(o = value) && o.send(associated_model_config.object_label_method)
end
+ register_instance_option :inline_add do
+ true
+ end
+
+ register_instance_option :inline_edit do
+ true
+ end
+
+
def editable?
(nested_form || abstract_model.model.new.respond_to?("#{self.name}_id=")) && super
end
diff --git a/lib/rails_admin/config/fields/types/serialized.rb b/lib/rails_admin/config/fields/types/serialized.rb
index 2792bcc17e..0281ad9004 100644
--- a/lib/rails_admin/config/fields/types/serialized.rb
+++ b/lib/rails_admin/config/fields/types/serialized.rb
@@ -13,7 +13,9 @@ class Serialized < RailsAdmin::Config::Fields::Types::Text
end
def parse_input(params)
- params[name] = (params[name].blank? ? nil : YAML.safe_load(params[name])) if params[name].is_a?(::String)
+ if params[name].is_a?(::String)
+ params[name] = (params[name].blank? ? nil : (YAML.safe_load(params[name]) || nil))
+ end
end
end
end
diff --git a/lib/rails_admin/config/model.rb b/lib/rails_admin/config/model.rb
index 6d1fa0a892..e16ff594c7 100644
--- a/lib/rails_admin/config/model.rb
+++ b/lib/rails_admin/config/model.rb
@@ -82,6 +82,10 @@ def pluralize(count)
@navigation_label ||= (parent_module = abstract_model.model.parent) != Object ? parent_module.to_s : nil
end
+ register_instance_option :navigation_icon do
+ nil
+ end
+
# Act as a proxy for the base section configuration that actually
# store the configurations.
def method_missing(m, *args, &block)
diff --git a/lib/rails_admin/version.rb b/lib/rails_admin/version.rb
index 490e0f97d1..6cb552bed3 100644
--- a/lib/rails_admin/version.rb
+++ b/lib/rails_admin/version.rb
@@ -2,7 +2,7 @@ module RailsAdmin
class Version
MAJOR = 0 unless defined? MAJOR
MINOR = 4 unless defined? MINOR
- PATCH = 5 unless defined? PATCH
+ PATCH = 7 unless defined? PATCH
PRE = nil unless defined? PRE
class << self
diff --git a/spec/controllers/rails_admin/main_controller_spec.rb b/spec/controllers/rails_admin/main_controller_spec.rb
index bb896f77cc..ca70646746 100644
--- a/spec/controllers/rails_admin/main_controller_spec.rb
+++ b/spec/controllers/rails_admin/main_controller_spec.rb
@@ -38,6 +38,24 @@
end
describe "#get_sort_hash" do
+ context "options sortable is a hash" do
+ before do
+ RailsAdmin.config('Player') do
+ configure :team do
+ sortable do
+ :'team.name'
+ end
+ end
+ end
+ end
+
+ it "returns the option with no changes" do
+ controller.params = { :sort => "team", :model_name =>"players" }
+ expect(controller.send(:get_sort_hash, RailsAdmin.config(Player))).to eq({:sort=>:"team.name", :sort_reverse=>true})
+ end
+ end
+
+
it "works with belongs_to associations with label method virtual" do
controller.params = { :sort => "parent_category", :model_name =>"categories" }
expect(controller.send(:get_sort_hash, RailsAdmin.config(Category))).to eq({:sort=>"categories.parent_category_id", :sort_reverse=>true})
diff --git a/spec/dummy_app/app/mongoid/field_test.rb b/spec/dummy_app/app/mongoid/field_test.rb
index 0816d9ebf0..780c3941df 100644
--- a/spec/dummy_app/app/mongoid/field_test.rb
+++ b/spec/dummy_app/app/mongoid/field_test.rb
@@ -11,6 +11,7 @@ class FieldTest
field :big_decimal_field, :type => BigDecimal
field :boolean_field, :type => Boolean
field :bson_object_id_field, :type => RailsAdmin::Adapters::Mongoid::ObjectId
+ field :bson_binary_field, :type => Moped::BSON::Binary
field :date_field, :type => Date
field :datetime_field, :type => DateTime
field :time_with_zone_field, :type => ActiveSupport::TimeWithZone
@@ -30,7 +31,7 @@ class FieldTest
field :protected_field, :type => String
has_mongoid_attached_file :paperclip_asset, :styles => { :thumb => "100x100>" }
- basic_accessible_fields = [:comment_attributes, :nested_field_tests_attributes, :embed_attributes, :embeds_attributes, :dragonfly_asset, :remove_dragonfly_asset, :retained_dragonfly_asset, :carrierwave_asset, :carrierwave_asset_cache, :remove_carrierwave_asset, :paperclip_asset, :delete_paperclip_asset, :comment_id, :name, :array_field, :big_decimal_field, :boolean_field, :bson_object_id_field, :date_field, :datetime_field, :time_with_zone_field, :default_field, :float_field, :hash_field, :integer_field, :object_field, :range_field, :string_field, :symbol_field, :text_field, :time_field, :created_at, :updated_at, :format]
+ basic_accessible_fields = [:comment_attributes, :nested_field_tests_attributes, :embed_attributes, :embeds_attributes, :dragonfly_asset, :remove_dragonfly_asset, :retained_dragonfly_asset, :carrierwave_asset, :carrierwave_asset_cache, :remove_carrierwave_asset, :paperclip_asset, :delete_paperclip_asset, :comment_id, :name, :array_field, :big_decimal_field, :boolean_field, :bson_object_id_field, :bson_binary_field, :date_field, :datetime_field, :time_with_zone_field, :default_field, :float_field, :hash_field, :integer_field, :object_field, :range_field, :string_field, :symbol_field, :text_field, :time_field, :created_at, :updated_at, :format]
attr_accessible *basic_accessible_fields
attr_accessible *(basic_accessible_fields + [:restricted_field, {:as => :custom_role}])
attr_accessible *(basic_accessible_fields + [:protected_field, {:as => :extra_safe_role}])
diff --git a/spec/integration/config/edit/rails_admin_config_edit_spec.rb b/spec/integration/config/edit/rails_admin_config_edit_spec.rb
index eef2ba178f..97e2ef008e 100644
--- a/spec/integration/config/edit/rails_admin_config_edit_spec.rb
+++ b/spec/integration/config/edit/rails_admin_config_edit_spec.rb
@@ -593,6 +593,79 @@ class HelpTest < Tableless
expect(find("#team_division_id_field .help-block")).to have_content("Optional")
expect(find("#team_name_field .help-block")).to have_content("Required")
end
+
+ it "can hide the add button on an associated field" do
+ RailsAdmin.config Player do
+ edit do
+ field :team do
+ inline_add false
+ end
+ field :draft do
+ inline_add false
+ end
+ field :comments do
+ inline_add false
+ end
+ end
+ end
+ visit new_path(:model_name => "player")
+ should have_no_selector('a', :text => 'Add a new Team')
+ should have_no_selector('a', :text => 'Add a new Draft')
+ should have_no_selector('a', :text => 'Add a new Comment')
+ end
+
+ it "can show the add button on an associated field" do
+ RailsAdmin.config Player do
+ edit do
+ field :team do
+ inline_add true
+ end
+ field :draft do
+ inline_add true
+ end
+ field :comments do
+ inline_add true
+ end
+ end
+ end
+ visit new_path(:model_name => "player")
+ should have_selector('a', :text => 'Add a new Team')
+ should have_selector('a', :text => 'Add a new Draft')
+ should have_selector('a', :text => 'Add a new Comment')
+ end
+
+ it "can hide the edit button on an associated field" do
+ RailsAdmin.config Player do
+ edit do
+ field :team do
+ inline_edit false
+ end
+ field :draft do
+ inline_edit false
+ end
+ end
+ end
+ visit new_path(:model_name => "player")
+ should have_no_selector('a', :text => 'Edit this Team')
+ should have_no_selector('a', :text => 'Edit this Draft')
+ end
+
+ it "can show the edit button on an associated field" do
+ RailsAdmin.config Player do
+ edit do
+ field :team do
+ inline_edit true
+ end
+ field :draft do
+ inline_edit true
+ end
+ end
+ end
+ visit new_path(:model_name => "player")
+ should have_selector('a', :text => 'Edit this Team')
+ should have_selector('a', :text => 'Edit this Draft')
+ end
+
end
describe "bindings" do
@@ -1023,4 +1096,5 @@ def color_enum
should have_selector(".color_type input")
end
end
+
end
diff --git a/spec/rails_admin/adapters/mongoid_spec.rb b/spec/rails_admin/adapters/mongoid_spec.rb
index afa362ec62..3f47b0d323 100644
--- a/spec/rails_admin/adapters/mongoid_spec.rb
+++ b/spec/rails_admin/adapters/mongoid_spec.rb
@@ -243,12 +243,24 @@ class MongoEmbedded
embedded_in :mongo_embeds_many
end
+ class MongoRecursivelyEmbedsOne
+ include Mongoid::Document
+ recursively_embeds_one
+ end
+
+ class MongoRecursivelyEmbedsMany
+ include Mongoid::Document
+ recursively_embeds_many
+ end
+
expect(lambda{ RailsAdmin::AbstractModel.new(MongoEmbedsOne).associations }).to raise_error(RuntimeError,
"Embbeded association without accepts_nested_attributes_for can't be handled by RailsAdmin,\nbecause embedded model doesn't have top-level access.\nPlease add `accepts_nested_attributes_for :mongo_embedded' line to `MongoEmbedsOne' model.\n"
)
expect(lambda{ RailsAdmin::AbstractModel.new(MongoEmbedsMany).associations }).to raise_error(RuntimeError,
"Embbeded association without accepts_nested_attributes_for can't be handled by RailsAdmin,\nbecause embedded model doesn't have top-level access.\nPlease add `accepts_nested_attributes_for :mongo_embeddeds' line to `MongoEmbedsMany' model.\n"
)
+ expect(lambda{ RailsAdmin::AbstractModel.new(MongoRecursivelyEmbedsOne).associations }).not_to raise_error
+ expect(lambda{ RailsAdmin::AbstractModel.new(MongoRecursivelyEmbedsMany).associations }).not_to raise_error
end
it "works with inherited embeds_many model" do
@@ -276,10 +288,10 @@ class MongoEmbedsChild < MongoEmbedsParent; end
it "maps Mongoid column types to RA types" do
expect(@abstract_model.properties.select{|p| %w(_id array_field big_decimal_field
- boolean_field bson_object_id_field date_field datetime_field time_with_zone_field default_field float_field
- hash_field integer_field name object_field range_field short_text string_field subject
- symbol_field text_field time_field title).
- include? p[:name].to_s}).to match_array [
+ boolean_field bson_object_id_field bson_binary_field date_field datetime_field
+ time_with_zone_field default_field float_field hash_field integer_field name
+ object_field range_field short_text string_field subject symbol_field text_field
+ time_field title).include? p[:name].to_s}).to match_array [
{ :name => :_id,
:pretty_name => "Id",
:nullable? => true,
diff --git a/spec/rails_admin/config/fields/base_spec.rb b/spec/rails_admin/config/fields/base_spec.rb
index a9ae9392b6..ee8ec003a2 100644
--- a/spec/rails_admin/config/fields/base_spec.rb
+++ b/spec/rails_admin/config/fields/base_spec.rb
@@ -394,6 +394,18 @@ class FieldVisibilityTest < Tableless
expect(editable).to be_false
end
+ it "yells for non attr_accessible fields specified role if config.yell_for_non_accessible_fields is true" do
+ RailsAdmin.config do |config|
+ config.yell_for_non_accessible_fields = true
+ config.model FieldTest do
+ field :protected_field
+ end
+ end
+ Rails.logger.should_receive(:debug).with {|msg| msg =~ /Please add 'attr_accessible :protected_field, :as => :admin'/ }
+ editable = RailsAdmin.config(FieldTest).field(:protected_field).with(:object => FactoryGirl.create(:field_test), :view => double(:controller => double(:_attr_accessible_role => :admin))).editable?
+ expect(editable).to be_false
+ end
+
it "does not yell for non attr_accessible fields if config.yell_for_non_accessible_fields is false" do
RailsAdmin.config do |config|
config.yell_for_non_accessible_fields = false
diff --git a/spec/rails_admin/config_spec.rb b/spec/rails_admin/config_spec.rb
index adc60a1a4f..f1256cffc3 100644
--- a/spec/rails_admin/config_spec.rb
+++ b/spec/rails_admin/config_spec.rb
@@ -267,6 +267,21 @@
expect(RailsAdmin.config.visible_models(:controller => double(:_current_user => double(:role => :admin), :authorized? => true)).map(&:abstract_model).map(&:model)).to match_array [FieldTest, Comment]
end
+
+ it "basically does not contain embedded model except model using recursively_embeds_many or recursively_embeds_one", :mongoid => true do
+ class RecursivelyEmbedsOne
+ include Mongoid::Document
+ recursively_embeds_one
+ end
+ class RecursivelyEmbedsMany
+ include Mongoid::Document
+ recursively_embeds_many
+ end
+ RailsAdmin.config do |config|
+ config.included_models = [FieldTest, Comment, Embed, RecursivelyEmbedsMany, RecursivelyEmbedsOne]
+ end
+ expect(RailsAdmin.config.visible_models(:controller => double(:_current_user => double(:role => :admin), :authorized? => true)).map(&:abstract_model).map(&:model)).to match_array [FieldTest, Comment, RecursivelyEmbedsMany, RecursivelyEmbedsOne]
+ end
end
end