AUI textboxlist - retrieve removed elements value - javascript

I am using Liferay 6.2 and want to retrieve the value of removed element from a Textboxlist component. I have stored a list of values in a hiddenInput element, and I display the list in a Textboxlist. As I remove the element, I want to update the values stores in the hidden input element. But I do not know how to retrieve the removed element.
AUI().ready('aui-textboxlist-deprecated', function (A) {
var source = A.one('#hiddenInput').val().split(',');
var tagslist = new A.TextboxList({
contentBox: '#demo',
dataSource: source,
matchKey: 'name',
schema: {
resultFields: ['key', 'name']
},
schemaType: 'json',
typeAhead: true,
width: 500
}).render();
var values = A.one('#hiddenInput').val().split(',');
A.each(values, tagslist.add, tagslist);
var updateHiddenInput = function (event) {
//how to get the removed element?
}
tagslist.entries.after('remove', updateHiddenInput);
});
How to achieve this?

As #jbalsas said in the comments:
If you just need the label, then you can get it using event.attrName. If you need to work with the element, it is passed in event.item.entry.
So you should be able to do it like this:
var updateHiddenInput = function (event) {
var hiddenInput = A.one('#hiddenInput');
hiddenInput.val(hiddenInput.val() + ',' + event.item.entry); // or event.attrName
}

Related

send function variable to external ajax call

I have a modal that has a element of text with an icon
<span class="edit-comment-icon" title="Edit"></span>
and when the icon is clicked it turns the h3 into a textarea:
$('.edit-comment-icon').click(function(){
var _this = $(this),
h3Tag =_this.prev()[0],
h3Value = productComment;
var input = $('<textarea />', {
val: h3Value,
type: "text",
class:'uk-width-4-10',
style: 'padding: 5px',
rows: '5'
});
h3Tag.replaceWith(input[0]);
$('.edit-comment-icon').hide();
$('.save-comment-button').removeClass('uk-hidden').css('font-size','20px');
})
The issue is I have an external ajax call that is using another set of data for submission, but I need to add into that data the value that is in this text area at the time they hit submit.
So if the header said "TEST" and they edit, and change it to "testing" in the textarea and hit save/submit, I need to pass "testing" to my ajax call along with the current data it's receiving:
$('#saveButton').click(function(){
$ajax({
url: 'url',
data: dataset /*but need to add textarea value to this*/
});
})
How can I add the textarea value from this other function so that it is sent in the external ajax call?
Just use a selector that targets the textarea element inside the modal element, for instance if your modal element had the class my-modal you could use .my-modal textarea
$('#saveButton').click(function(){
var value = $('.my-modal textarea').val();
var dataset = {
myTextarea:value
//and/or other properties
};
//or if dataset is already created
dataset.myTextarea = value;
$ajax({
url: 'url',
data: dataset
});
})
If there are more than one textarea inside the modal you would need to add an id or some unique class to that textarea
var input = $('<textarea />', {
val: h3Value,
type: "text",
class:'for-h3 uk-width-4-10',
style: 'padding: 5px',
rows: '5'
});
and then in the callback
var value = $('.my-modal .for-h3').val();

Render angular directives in Selectize.js items

I am using angular-selectize to use Selectize.js in my angular project.
To use custom items in Selectize.js selector, I am using Selectize.js' render option:
render: {
item: function(item, escape) {
var avatar = '<div>' +
'<span avatars="\'' + escape(item._id) +'\'" class="avatars">' +
'</span>' +
escape(item.nick) +
'</div>';
var compiledAvatar = $compile(avatar)($rootScope);
$timeout();
return compiledAvatar.html();
},
where avatars is a custom directive with asychronous behaviour
The problem is that the render.item function expects an HTML string as an output but:
There is no way of returning a rendered or "$compileed" HTML string in a synchronous way as expected by render.item method.
I do not know how to render that item's elements afterwards when they have already been added to the DOM.
Note that although $compile is called, returned string would not be the expected compiled result but the string before compilation due to the asynchronous nature of $compile.
One idea is to use DOM manipulation which is not the most recommended Angular way, but I got it working on this plunker. and a second one with custom directive and randomized data to simulate your compiled avatar.
To simulate your asynchronous call, I use ngResource. My render function returns a string "<div class='compiledavatar'>Temporary Avatar</div>" with a special class markup compiledavatar. For a second or two, you will see Temporary Avatar as you select an element. When the ngResource calls finishes I look for the element with class compiledavatar and then replace the html with what I downloaded. Here is the full code:
var app = angular.module('plunker', ['selectize', 'ngResource']);
app.controller('MainCtrl', function($scope, $resource, $document) {
var vm = this;
vm.name = 'World';
vm.$resource = $resource;
vm.myModel = 1;
vm.$document = $document;
vm.myOptions = [{
id: 1,
title: 'Spectrometer'
}, {
id: 2,
title: 'Star Chart'
}, {
id: 3,
title: 'Laser Pointer'
}];
vm.myConfig = {
create: true,
valueField: 'id',
labelField: 'title',
delimiter: '|',
placeholder: 'Pick something',
onInitialize: function(selectize) {
// receives the selectize object as an argument
},
render: {
item: function(item, escape) {
var label = item.title;
var caption = item.id;
var Stub = vm.$resource('mydata', {});
// This simulates your asynchronous call
Stub.get().$promise.then(function(s) {
var result = document.getElementsByClassName("compiledavatar")
angular.element(result).html(s.compiledAvatar);
// Once the work is done, remove the class so next time this element wont be changed
// Remove class
var elems = document.querySelectorAll(".compiledavatar");
[].forEach.call(elems, function(el) {
el.className = el.className.replace(/compiledavatar/, "");
});
});
return "<div class='compiledavatar'>Temporary Avatar</div>"
}
},
// maxItems: 1
};
});
To simulate the JSON API I just created a file in plunker mydata:
{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar</span></div>"
}
Of course your compiled function should return you something different every calls. Me it gives me the same to demonstrate the principle.
In addition, if your dynamic code is an Agular directive, here is a second plunker with a custom directive and randomized data so you can better see the solution:
The data include a custom directive my-customer:
[{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar #1 <my-customer></my-customer></span></div>"
},
{
"compiledAvatar": "<div><span style='display: block; color: black; font-size: 14px;'>an avatar #2 <my-customer></my-customer></span></div>"
},
(...)
The directive is defined as:
app.directive('myCustomer', function() {
return {
template: '<div>and a custom directive</div>'
};
});
And the main difference in the app is that you have to add $compile when replacing the HTML and the text should show An avatar #(number) and a custom directive. I get an array of json value and use a simple random to pick a value. Once the HTML is replaced I remove the class, so next time only the last added element will be changed.
Stub.query().$promise.then(function(s) {
var index = Math.floor(Math.random() * 10);
var result = document.getElementsByClassName("compiledavatar")
angular.element(result).html($compile(s[index].compiledAvatar)($scope));
// Remove class
var elems = document.querySelectorAll(".compiledavatar");
[].forEach.call(elems, function(el) {
el.className = el.className.replace(/compiledavatar/, "");
});
});
Also, I looked at selectize library and you cant return a promise... as it does a html.replace on the value returned by render. This is why I went to the route of a temporary string with a class to retrieve later and update.
Let me know if that helps.
This answer is based in the helpful answer by #gregori with the following differences:
Take into account Selectize.js' Render Cache. The standard behaviour of Selectize.js is that the items are cached as returned by the render function, and not with the modifications we have done to them. After adding and deleting some elements, the cached and not the modified version would be displayed if we do not update the render cache acordingly.
Using random id's to identify the elements to select to be manipulated from DOM.
Using watchers to know when the compilation has been done
First, we define a method to modify the selectize.js render cache:
scope.selectorCacheUpdate = function(key, value, type){
var cached = selectize.renderCache[type][key];
// update cached element
var newValue = angular.element(cached).html(value);
selectize.renderCache[type][key] = newValue[0].outerHTML;
return newValue.html();
};
Then, the render function is defined as follows:
function renderAvatar(item, escape, type){
// Random id used to identify the element
var randomId = Math.floor(Math.random() * 0x10000000).toString(16);
var avatar =
'<div id="' + randomId + '">' +
'<span customAvatarTemplate ...></span>' +
...
'</div>';
var compiled = $compile(avatar)($rootScope);
// watcher to see when the element has been compiled
var destroyWatch = $rootScope.$watch(
function (){
return compiled[0].outerHTML;
},
function (newValue, oldValue){
if(newValue !== oldValue){
var elem = angular.element(document.getElementById(randomId));
var rendered = elem.scope().selectorCacheUpdate(item._id, compiled.html(), type);
// Update DOM element
elem.html(rendered);
destroyWatch();
}
}
);
});
return avatar;
}
Note: The key for the render cache is the valueField of the selectize items, in this case, _id
Finally, we add this function as a selectize render function in the selectize configuration object:
config = {
...
render: {
item: function(i,e){
return renderAvatar(i, e, 'item');
},
option: function(i,e){
return renderAvatar(i, e, 'option');
}
},
...
}
For more details, see how this solution has been added to the application that motivated this question: https://github.com/P2Pvalue/teem/commit/968a437e58c5f1e70e80cc6aa77f5aefd76ba8e3.

Disable multiple dateField's

I have a checkbox that controls 4 dateFields in ExtJs. I want to be able to give them a common property to be able to disable all fields with one command.
I assume there is a simpler way to do that. It works, but its a big block of code.
This is the checkBox's change event implementation so far:
change: function (cmp, newValue, oldValue, eOpts) {
var dt1 = cmp.up().down('#Dtf1');
dt1.setDisabled(newValue);
var dt2 = cmp.up().down('#Dtf2');
dt2.setDisabled(newValue);
var dt3 = cmp.up().down('#Dtf3');
dt3.setDisabled(newValue);
var dt4 = cmp.up().down('#Dtf4');
dt4.setDisabled(newValue); }
You can add an attribute for example:
{
xtype: 'textfieid',
itemId: 'Dtf1',
dtf: true
}
Then you'll be able to query like:
cmp.up().query('[dtf=true]').forEach(function(item){
item.setDisabled(newValue);
});
Or can also use a query, like:
cmp.up().query('#Dtf1, #Dtf2, #Dtf3, #Dtf4').forEach(function(item){
item.setDisabled(newValue);
});

Kendo UI Web - MultiSelect: select an option more than once

I'm currently facing a problem with the Kendo UI MultiSelect widget for selecting an option more than once. For example, in the image below I want to select Schindler's List again after selecting The Dark Knight, but unfortunately the MultiSelect widget behaves more like a set than an ordered list, i.e. repetitive selection is not allowed. Is there actually a proper way to achieve this? Any workarounds?
That's the intended behavior of the multi-select control and there is no simple way to make it do what you want using the available configuration options. Possible workarounds are ...
Creating a custom multi-select widget
Something like this should work (note that I haven't tested this much - it lets you add multiples and keeps the filter intact though):
(function ($) {
var ui = kendo.ui,
MultiSelect = ui.MultiSelect;
var originalRender = MultiSelect.fn._render;
var originalSelected = MultiSelect.fn._selected;
var CustomMultiSelect = MultiSelect.extend({
init: function (element, options) {
var that = this;
MultiSelect.fn.init.call(that, element, options);
},
options: {
name: 'CustomMultiSelect'
},
_select: function (li) {
var that = this,
values = that._values,
dataItem,
idx;
if (!that._allowSelection()) {
return;
}
if (!isNaN(li)) {
idx = li;
} else {
idx = li.data("idx");
}
that.element[0].children[idx].selected = true;
dataItem = that.dataSource.view()[idx];
that.tagList.append(that.tagTemplate(dataItem));
that._dataItems.push(dataItem);
values.push(that._dataValue(dataItem));
that.currentTag(null);
that._placeholder();
if (that._state === "filter") {
that._state = "accept";
}
console.log(this.dataSource.view());
},
_render: function (data) {
// swapping out this._selected keeps filtering functional
this._selected = dummy;
return originalRender.call(this, data);
this._selected = originalSelected;
}
});
function dummy() { return null; }
ui.plugin(CustomMultiSelect);
})(jQuery);
Demo here.
Using a dropdown list
Use a simple dropdown list (or ComboBox) and bind the select event to append to your list (which you have to create manually).
For example:
var mySelectedList = [];
$("#dropdownlist").kendoDropDownList({
select: function (e) {
var item = e.item;
var text = item.text();
// store your selected item in the list
mySelectedList.push({
text: text
});
// update the displayed list
$("#myOrderedList").append("<li>" + text + "</li>");
}
});
Then you could bind clicks on those list elements to remove elements from the list. The disadvantage of that is that it requires more work to make it look "pretty" (you have to create and combine your own HTML, css, images etc.).

How to remove label after removing a component?

By removing a field in a fieldset, it's label is not removed. How can I remove it? I'm using ExtJS 2.0.2. Code follows:
var predicateArguments= new Ext.form.FieldSet({
id:'predicate_arguments',
layout:'form',
title:'Predicate Arguments',
defaultType:'textfield',
autoHeight:true,
autoWidth:true,
autoScroll:true,
items:[
{
xtype:'textarea',
id:'description',
fieldLabel:'Description',
name:'description',
readOnly:true
}
],
buttons: [
{text: 'Add',
handler:function(){
//this will submit the form's fields
predicatesForm.getForm().submit();
}},
{text: 'Reset Fields',
handler:function(){
//this will reset the form's fields
predicatesForm.getForm().reset();
}}
]
})
var predicatesForm = new Ext.FormPanel({
id:'predicates-form',
region:'center',
split:true,
labelAlign:'right',
layout:'column',
items:[availablePredicatesGrid, predicateArguments]
})
var dateField = {xtype:'datefield',
fieldLabel:'Date',
name:'date',
id:'date_field'
}
var numberField = {xtype:'numberfield',
fieldLabel:'Number',
name:'number',
id:'number_field'
}
//when a rule is clicked do the following...
availablePredicatesGrid.getSelectionModel().on('rowselect', function(sm,_rowIndex,_rule){
predicatesForm.getForm().loadRecord(_rule);
var _ruleType= _rule.store.getAt(_rowIndex).get('type');
//removes previous existing fields (but not labels!)
predicateArguments.remove('date_field');
predicateArguments.remove('number_field');
//creates new fields according to the selected rule types
for(i=0; i<_ruleType.length; i++){
if(_ruleType[i]=='date'){
predicateArguments.add(dateField);
predicateArguments.doLayout();
}
else if(_ruleType[i]=='number'){
predicateArguments.add(numberField);
predicateArguments.doLayout();
}
}
});
Instead of calling remove on your Ext.Element reference, do this instead:
Ext.getCmp('id_of_your_form_field').getEl().parent().parent().remove();
EDIT: this is intended for use on form field components, as it removes the wrapper div containing the label and the component
I don't know extjs, but, normally, how would you do this (and how you should do this) is by simply adding the input and its label in a or and then, when you want to remove the input (and its label in the same time), just do:
obj = document.getElementById( ID-OF-ELEMENT )
obj.parentNode.html = '';
or just delete its parent in any other way you might find to not have empty spans lying around:
obj.parentNode.parentNode.removeChild( obj.parentNode );
or, if you know that the label is just before the input, you can just do:
obj.parentNode.removeChild( obj.previousSibling );
or just combine them to suit your purpose

Categories

Resources