mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-22 01:58:47 +02:00
Advanced search: WIP POC, integration with endpoint.
SVN:b1162[5412]
This commit is contained in:
@@ -54,6 +54,17 @@ class NiceWebPage extends WebPage
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_raw.js');
|
||||
$this->add_linked_script(utils::GetAbsoluteUrlAppRoot().'js/search/search_form_criteria_string.js');
|
||||
// TODO: Remove
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:Default:Empty');
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:Default:NotEmpty');
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:Default:Equals');
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:String:Contains');
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:String:StartsWith');
|
||||
$this->add_dict_entry('UI:Search:Criteria:Operator:String:EndsWith');
|
||||
$this->add_dict_entry('UI:Button:Apply');
|
||||
$this->add_dict_entry('UI:Button:Cancel');
|
||||
$this->add_dict_entry('UI:Button:More');
|
||||
$this->add_dict_entry('UI:Button:Less');
|
||||
$this->add_ready_script(
|
||||
<<< EOF
|
||||
//add new widget called TruncatedList to properly display truncated lists when they are sorted
|
||||
|
||||
@@ -789,6 +789,13 @@ div.HRDrawer {
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria.locked {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle {
|
||||
display: inline-block;
|
||||
transition: all 0.2s ease-in-out;
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_toggle.opened {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
.search_form_handler .sf_criterion_area .search_form_criteria .sfc_title {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
@@ -880,8 +880,11 @@ div.HRDrawer {
|
||||
|
||||
}
|
||||
.sfc_toggle{
|
||||
&.opened{
|
||||
display: inline-block;
|
||||
transition: all 0.2s ease-in-out;
|
||||
|
||||
&.opened{
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
}
|
||||
.sfc_title{
|
||||
|
||||
@@ -389,6 +389,8 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'UI:Button:ChangePassword' => ' Change Password ',
|
||||
'UI:Button:ResetPassword' => ' Reset Password ',
|
||||
'UI:Button:Insert' => 'Insert',
|
||||
'UI:Button:More' => 'More',
|
||||
'UI:Button:Less' => 'Less',
|
||||
|
||||
'UI:SearchToggle' => 'Search',
|
||||
'UI:ClickToCreateNew' => 'Create a new %1$s',
|
||||
@@ -1367,4 +1369,12 @@ When associated with a trigger, each action is given an "order" number, specifyi
|
||||
'UI:Button:ResetImage' => 'Recover the previous image',
|
||||
'UI:Button:RemoveImage' => 'Remove the image',
|
||||
'UI:UploadNotSupportedInThisMode' => 'The modification of images or files is not supported in this mode.',
|
||||
|
||||
// TODO: Reorganize those entries with other search entries and make entries for other languages.
|
||||
'UI:Search:Criteria:Operator:Default:Empty' => 'Is empty:',
|
||||
'UI:Search:Criteria:Operator:Default:NotEmpty' => 'Is not empty:',
|
||||
'UI:Search:Criteria:Operator:Default:Equals' => 'Equals:',
|
||||
'UI:Search:Criteria:Operator:String:Contains' => 'Contains:',
|
||||
'UI:Search:Criteria:Operator:String:StartsWith' => 'Starts with:',
|
||||
'UI:Search:Criteria:Operator:String:EndsWith' => 'Ends with:',
|
||||
));
|
||||
|
||||
@@ -9,6 +9,7 @@ $(function()
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Default values for the criteria
|
||||
ref: '',
|
||||
operator: '=',
|
||||
values: [],
|
||||
@@ -18,9 +19,33 @@ $(function()
|
||||
field: {
|
||||
label: '',
|
||||
},
|
||||
|
||||
// Available operators (merged with derived widgets, ordered and then copied to this.operators)
|
||||
available_operators: {
|
||||
'=': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:Equals'),
|
||||
'code': 'equals',
|
||||
'rank': 10,
|
||||
},
|
||||
'empty': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:Empty'),
|
||||
'code': 'empty',
|
||||
'rank': 90,
|
||||
},
|
||||
'not_empty': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:Default:NotEmpty'),
|
||||
'code': 'not_empty',
|
||||
'rank': 100,
|
||||
},
|
||||
},
|
||||
|
||||
is_modified: false, // TODO: change this on value change and remove oql property value
|
||||
},
|
||||
|
||||
// Operators
|
||||
operators: {},
|
||||
|
||||
// Form handler
|
||||
handler: null,
|
||||
|
||||
// the constructor
|
||||
@@ -30,33 +55,13 @@ $(function()
|
||||
|
||||
this.element.addClass('search_form_criteria');
|
||||
|
||||
this._orderOperators();
|
||||
|
||||
// Link search form handler
|
||||
this.handler = this.element.closest('.search_form_handler');
|
||||
|
||||
// GetData
|
||||
this.element.bind('itop.search.criteria.get_data', function(oEvent, oData){
|
||||
return me._onGetData(oData);
|
||||
});
|
||||
// Get/SetCurrentValues callbacks handler
|
||||
this.element.bind('itop.search.criteria.get_current_values itop.search.criteria.set_current_values', function(oEvent, oData){
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var callback = me.options[oEvent.type+'_callback'];
|
||||
|
||||
if(typeof callback === 'string')
|
||||
{
|
||||
return me[callback](oEvent, oData);
|
||||
}
|
||||
else if(typeof callback === 'function')
|
||||
{
|
||||
return callback(me, oEvent, oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('search form criteria: callback type must be a function or a existing function name of the widget');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
// Bind events
|
||||
this._bindEvents();
|
||||
|
||||
this._prepareElement();
|
||||
},
|
||||
@@ -85,6 +90,43 @@ $(function()
|
||||
|
||||
|
||||
// Protected methods
|
||||
// - Order available operators
|
||||
_orderOperators: function()
|
||||
{
|
||||
console.log(this.options.available_operators);
|
||||
|
||||
},
|
||||
// - Bind external events
|
||||
_bindEvents: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// Get criteria data
|
||||
this.element.bind('itop.search.criteria.get_data', function(oEvent, oData){
|
||||
return me._onGetData(oData);
|
||||
});
|
||||
|
||||
// Get/SetCurrentValues callbacks handler
|
||||
this.element.bind('itop.search.criteria.get_current_values itop.search.criteria.set_current_values', function(oEvent, oData){
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var callback = me.options[oEvent.type+'_callback'];
|
||||
|
||||
if(typeof callback === 'string')
|
||||
{
|
||||
return me[callback](oEvent, oData);
|
||||
}
|
||||
else if(typeof callback === 'function')
|
||||
{
|
||||
return callback(me, oEvent, oData);
|
||||
}
|
||||
else
|
||||
{
|
||||
console.log('search form criteria: callback type must be a function or a existing function name of the widget');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
},
|
||||
_remove: function()
|
||||
{
|
||||
this.element.remove();
|
||||
@@ -93,6 +135,25 @@ $(function()
|
||||
|
||||
|
||||
// Event callbacks
|
||||
// - Internal events
|
||||
_onButtonApply: function()
|
||||
{
|
||||
this._trace('TODO: Apply button');
|
||||
this.handler.triggerHandler('itop.search.criteria.value_changed');
|
||||
},
|
||||
_onButtonCancel: function()
|
||||
{
|
||||
this._trace('TODO: Cancel button');
|
||||
},
|
||||
_onButtonMore: function()
|
||||
{
|
||||
this.element.find('.sfc_form_group').addClass('advanced');
|
||||
},
|
||||
_onButtonLess: function()
|
||||
{
|
||||
this.element.find('.sfc_form_group').removeClass('advanced');
|
||||
},
|
||||
// - External events
|
||||
_onGetData: function(oData)
|
||||
{
|
||||
var oCriteriaData = {
|
||||
@@ -113,16 +174,16 @@ $(function()
|
||||
var me = this;
|
||||
|
||||
// Prepare base DOM structure
|
||||
//this.options.ref+' '+this.options.operator+' '+this.options.values
|
||||
this.element
|
||||
.append('<div class="sfc_title"></div>')
|
||||
.append('<div class="sfc_form_group"></div>')
|
||||
.append('<div class="sfc_form_group"><div class="sfc_fg_operators"></div><div class="sfc_fg_buttons"></div></div>')
|
||||
.append('<span class="sfc_toggle"><a class="fa fa-caret-down" href="#"></a></span>');
|
||||
|
||||
// Bind events
|
||||
// - Toggler
|
||||
this.element.find('.sfc_toggle, .sfc_title').on('click', function(){
|
||||
me.element.find('.sfc_form_group').toggle();
|
||||
me.element.find('.sfc_toggle').toggleClass('opened');
|
||||
});
|
||||
|
||||
// Removable / locked decoration
|
||||
@@ -138,9 +199,64 @@ $(function()
|
||||
this.element.append('<div class="sfc_locked"><span class="fa fa-lock"></span></div>');
|
||||
}
|
||||
|
||||
// Form group
|
||||
this._prepareOperators();
|
||||
this._prepareButtons();
|
||||
|
||||
// Fill criteria
|
||||
// - Title
|
||||
this._setTitle();
|
||||
},
|
||||
// - Prepare the available operators for the criteria
|
||||
// Meant for overloading.
|
||||
_prepareOperators: function()
|
||||
{
|
||||
for(var sOpIdx in this.options.available_operators)
|
||||
{
|
||||
var oOp = this.options.available_operators[sOpIdx];
|
||||
var sMethod = '_prepare' + this._toCamelCase(oOp.code) + 'Operator';
|
||||
|
||||
// Create DOM element from template
|
||||
var oOpElem = $(this._getOperatorTemplate()).uniqueId();
|
||||
|
||||
// Prepare operator's base elements
|
||||
this._prepareOperator(oOpElem, oOp);
|
||||
|
||||
// Prepare operator's specific elements
|
||||
if(this[sMethod] !== undefined)
|
||||
{
|
||||
this[sMethod](oOpElem, oOp);
|
||||
}
|
||||
else
|
||||
{
|
||||
this._prepareDefaultOperator(oOpElem, oOp);
|
||||
}
|
||||
|
||||
// Append to form group
|
||||
oOpElem.appendTo(this.element.find('.sfc_fg_operators'));
|
||||
}
|
||||
},
|
||||
// - Prepare the buttons (DOM and events) for a criteria
|
||||
_prepareButtons: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// DOM elements
|
||||
this.element.find('.sfc_fg_buttons')
|
||||
.append('<button type="button" name="apply" class="sfc_fg_button sfc_fg_apply">' + Dict.S('UI:Button:Apply') + '</button>')
|
||||
.append('<button type="button" name="cancel" class="sfc_fg_button sfc_fg_cancel">' + Dict.S('UI:Button:Cancel') + '</button>')
|
||||
.append('<button type="button" name="more" class="sfc_fg_button sfc_fg_more">' + Dict.S('UI:Button:More') + '</button>')
|
||||
.append('<button type="button" name="less" class="sfc_fg_button sfc_fg_less">' + Dict.S('UI:Button:Less') + '</button>');
|
||||
|
||||
// Events
|
||||
this.element.find('.sfc_fg_button').on('click', function(oEvent){
|
||||
oEvent.preventDefault();
|
||||
oEvent.stopPropagation();
|
||||
|
||||
var sCallback = '_onButton' + me._toCamelCase($(this).attr('name'));
|
||||
me[sCallback]();
|
||||
});
|
||||
},
|
||||
// - Set the title element
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
@@ -151,7 +267,49 @@ $(function()
|
||||
}
|
||||
this.element.find('.sfc_title').text(sTitle);
|
||||
},
|
||||
// - Return a HTML template for operators
|
||||
_getOperatorTemplate: function()
|
||||
{
|
||||
return '<div class="sfc_fg_operator"><label><input type="radio" class="sfc_op_radio" name="operator" value="" /><span class="sfc_op_name"></span><span class="sfc_op_content"></span></label></div>';
|
||||
},
|
||||
|
||||
// Operators helpers
|
||||
_prepareOperator: function(oOpElem, oOp)
|
||||
{
|
||||
var sInputId = oOp.code + '_' + oOpElem.attr('id');
|
||||
|
||||
// Set label
|
||||
oOpElem.find('.sfc_op_name').text(oOp.label);
|
||||
oOpElem.find('> label').attr('for', sInputId);
|
||||
|
||||
// Set value
|
||||
oOpElem.find('.sfc_op_radio').val(oOpElem.id);
|
||||
oOpElem.find('.sfc_op_radio').attr('id', sInputId);
|
||||
|
||||
// Bind events
|
||||
// - Check radio button on click
|
||||
oOpElem.on('click', function(){
|
||||
oOpElem.find('.sfc_op_radio').prop('checked', true);
|
||||
});
|
||||
},
|
||||
_prepareDefaultOperator: function(oOpElem, oOp)
|
||||
{
|
||||
var me = this;
|
||||
|
||||
// DOM element
|
||||
var oOpContentElem = $('<input type="text" />');
|
||||
oOpContentElem.val(this._getValuesAsText());
|
||||
|
||||
oOpElem.append(oOpContentElem);
|
||||
},
|
||||
_prepareEmptyOperator: function(oOpElem, oOp)
|
||||
{
|
||||
// Do nothing as only the label is necessary
|
||||
},
|
||||
_prepareNotEmptyOperator: function(oOpElem, oOp)
|
||||
{
|
||||
// Do nothing as only the label is necessary
|
||||
},
|
||||
|
||||
// Values helpers
|
||||
// - Convert values to a standard string
|
||||
@@ -181,6 +339,21 @@ $(function()
|
||||
},
|
||||
|
||||
|
||||
// Global helpers
|
||||
// - Converts a snake_case string to CamelCase
|
||||
_toCamelCase: function(sString)
|
||||
{
|
||||
var aParts = sString.split('_');
|
||||
|
||||
for(var i in aParts)
|
||||
{
|
||||
aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
|
||||
}
|
||||
|
||||
return aParts.join('');
|
||||
},
|
||||
|
||||
|
||||
// Debug helpers
|
||||
// - Show a trace in the javascript console
|
||||
_trace: function(sMessage, oData)
|
||||
|
||||
@@ -55,6 +55,14 @@ $(function()
|
||||
// Remove toggler as it's a non sense here
|
||||
this.element.find('.sfc_toggle').remove();
|
||||
},
|
||||
_prepareOperators: function()
|
||||
{
|
||||
// Overloading function and doing nothing for this special kind of criteria.
|
||||
},
|
||||
_prepareButtons: function()
|
||||
{
|
||||
// Overloading function and doing nothing for this special kind of criteria.
|
||||
},
|
||||
_setTitle: function(sTitle)
|
||||
{
|
||||
if(sTitle === undefined)
|
||||
|
||||
@@ -9,8 +9,21 @@ $(function()
|
||||
// default options
|
||||
options:
|
||||
{
|
||||
// Overload default operator
|
||||
operator: 'contains',
|
||||
// Available operators
|
||||
available_operators: {
|
||||
'contains': {
|
||||
'label': Dict.S('UI:Search:Criteria:Operator:String:Contains'),
|
||||
'code': 'contains',
|
||||
'rank': 5,
|
||||
},
|
||||
'=': {
|
||||
'rank': false,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
|
||||
// the constructor
|
||||
_create: function()
|
||||
@@ -49,26 +62,26 @@ $(function()
|
||||
//------------------
|
||||
|
||||
// DOM element helpers
|
||||
_prepareElement: function()
|
||||
{
|
||||
var me = this;
|
||||
|
||||
this._super();
|
||||
|
||||
// TODO: Refactor this after UI mockups
|
||||
var oInputElem = $('<input type="text" />');
|
||||
oInputElem.on('change', function(){
|
||||
var sValue = $(this).val();
|
||||
|
||||
me.options.values = [{
|
||||
value: sValue,
|
||||
label: sValue,
|
||||
}];
|
||||
me._setTitle();
|
||||
|
||||
me.handler.triggerHandler('itop.search.criteria.value_changed');
|
||||
})
|
||||
.appendTo(this.element.find('.sfc_form_group'));
|
||||
},
|
||||
// _prepareElement: function()
|
||||
// {
|
||||
// var me = this;
|
||||
//
|
||||
// this._super();
|
||||
//
|
||||
// // TODO: Refactor this after UI mockups
|
||||
// var oInputElem = $('<input type="text" />');
|
||||
// oInputElem.on('change', function(){
|
||||
// var sValue = $(this).val();
|
||||
//
|
||||
// me.options.values = [{
|
||||
// value: sValue,
|
||||
// label: sValue,
|
||||
// }];
|
||||
// me._setTitle();
|
||||
//
|
||||
// me.handler.triggerHandler('itop.search.criteria.value_changed');
|
||||
// })
|
||||
// .appendTo(this.element.find('.sfc_form_group'));
|
||||
// },
|
||||
});
|
||||
});
|
||||
|
||||
@@ -41,14 +41,26 @@ $(function()
|
||||
],
|
||||
'fields': [
|
||||
// Structure
|
||||
// 'zlist': {
|
||||
// 'alias.code': {
|
||||
// 'class_alias': '',
|
||||
// 'class': '',
|
||||
// 'code': '',
|
||||
// 'label': '',
|
||||
// 'type': '',
|
||||
// 'allowed_values': {...},
|
||||
// 'class': '',
|
||||
// 'code': '',
|
||||
// 'label': '',
|
||||
// 'type': '',
|
||||
// 'allowed_values': {...},
|
||||
// },
|
||||
// },
|
||||
// 'others': {
|
||||
// 'alias.code': {
|
||||
// 'class_alias': '',
|
||||
// 'class': '',
|
||||
// 'code': '',
|
||||
// 'label': '',
|
||||
// 'type': '',
|
||||
// 'allowed_values': {...},
|
||||
// },
|
||||
// },
|
||||
],
|
||||
},
|
||||
'list_params': {}, // Passed through to the endpoint so it can render the list correctly regarding the context.
|
||||
@@ -204,15 +216,32 @@ $(function()
|
||||
|
||||
// Add fields
|
||||
// TODO: Find a widget to handle dropdown menu
|
||||
for(var sFieldRef in this.options.search.fields)
|
||||
// - From "search" zlist
|
||||
for(var sFieldRef in this.options.search.fields.zlist)
|
||||
{
|
||||
var oField = this.options.search.fields[sFieldRef];
|
||||
var oField = this.options.search.fields.zlist[sFieldRef];
|
||||
var oFieldElem = $('<li></li>')
|
||||
.addClass('sf_mc_field')
|
||||
.attr('data-field-ref', sFieldRef)
|
||||
.text(oField.label);
|
||||
this.elements.more_criterion.find('> .sf_mc_list').append(oFieldElem);
|
||||
}
|
||||
// - Others
|
||||
if(this.options.search.fields.others !== undefined)
|
||||
{
|
||||
this.elements.more_criterion.find('> .sf_mc_list').append('<li>==================</li>');
|
||||
this.elements.more_criterion.find('> .sf_mc_list').append('<li>|| TODO: Better separation ||</li>');
|
||||
this.elements.more_criterion.find('> .sf_mc_list').append('<li>==================</li>');
|
||||
for(var sFieldRef in this.options.search.fields.others)
|
||||
{
|
||||
var oField = this.options.search.fields.others[sFieldRef];
|
||||
var oFieldElem = $('<li></li>')
|
||||
.addClass('sf_mc_field')
|
||||
.attr('data-field-ref', sFieldRef)
|
||||
.text(oField.label);
|
||||
this.elements.more_criterion.find('> .sf_mc_list').append(oFieldElem);
|
||||
}
|
||||
}
|
||||
|
||||
// Bind events
|
||||
this.elements.more_criterion.find('.sf_mc_field').on('click', function(){
|
||||
@@ -260,8 +289,9 @@ $(function()
|
||||
// Add some informations from the field
|
||||
if(this._hasFieldDefinition(sRef))
|
||||
{
|
||||
var oFieldDef = this._getFieldDefinition(sRef);
|
||||
oData.field = {
|
||||
label: this.options.search.fields[sRef].label,
|
||||
label: oFieldDef.label,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -283,18 +313,24 @@ $(function()
|
||||
{
|
||||
var sType = null;
|
||||
|
||||
if(this.options.search.fields[sRef] !== undefined)
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
sType = this.options.search.fields[sRef].widget.toLowerCase();
|
||||
|
||||
// Make sure the criteria type is supported, otherwise we might try to initialize a unknown widget.
|
||||
if(this.options.supported_criterion_types.indexOf(sType) < 0)
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
sType = this.options.default_criteria_type;
|
||||
sType = this.options.search.fields[sListIdx][sRef].widget.toLowerCase();
|
||||
|
||||
// Make sure the criteria type is supported, otherwise we might try to initialize a unknown widget.
|
||||
if(this.options.supported_criterion_types.indexOf(sType) < 0)
|
||||
{
|
||||
sType = this.options.default_criteria_type;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback for unknown widget types or unknown field refs
|
||||
else
|
||||
if(sType === null)
|
||||
{
|
||||
sType = this.options.default_criteria_type;
|
||||
}
|
||||
@@ -321,7 +357,33 @@ $(function()
|
||||
// Field helpers
|
||||
_hasFieldDefinition: function(sRef)
|
||||
{
|
||||
return (this.options.search.fields[sRef] !== undefined);
|
||||
var bFound = false;
|
||||
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
bFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return bFound;
|
||||
},
|
||||
_getFieldDefinition: function(sRef)
|
||||
{
|
||||
var oFieldDef = false;
|
||||
|
||||
for(var sListIdx in this.options.search.fields)
|
||||
{
|
||||
if(this.options.search.fields[sListIdx][sRef] !== undefined)
|
||||
{
|
||||
oFieldDef = this.options.search.fields[sListIdx][sRef];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return oFieldDef;
|
||||
},
|
||||
|
||||
// Button handlers
|
||||
@@ -388,6 +450,18 @@ $(function()
|
||||
// TODO: Hide loader
|
||||
this._trace('Hide loader');
|
||||
},
|
||||
// - Converts a snake_case string to CamelCase
|
||||
_toCamelCase: function(sString)
|
||||
{
|
||||
var aParts = sString.split('_');
|
||||
|
||||
for(var i in aParts)
|
||||
{
|
||||
aParts[i] = aParts[i].charAt(0).toUpperCase() + aParts[i].substr(1);
|
||||
}
|
||||
|
||||
return aParts.join('');
|
||||
},
|
||||
|
||||
|
||||
// Debug helpers
|
||||
|
||||
Reference in New Issue
Block a user