I'm trying to set up a form on my site, and want to use some dynamic dropdown.
I found Selectize.js, which seems like a good solution, however I'm struggling to find out how to get the ID's from the selected option when I post the form.
As in user selects "Banana" and selectize should return 2 as value for the post
The obvious answer would of course be to change valueField to 'id' however that messes up the createFilter so that's a no go..
I've made a jsfiddle with what I have so far: http://jsfiddle.net/imfpa/Lh3anheq/16/
HTML:
<form>
<select id="item-type" placeholder="Choose type...">
</select>
</form>
javascript:
function hasOwnPropertyCaseInsensitive(obj, property) {
var props = [];
for (var i in obj) if (obj.hasOwnProperty(i)) props.push(i);
var prop;
while (prop = props.pop()) if (prop.toLowerCase() === property.toLowerCase()) return true;
return false;
}
var REGEX = '[a-zA-ZæøåÆØÅ][a-zA-ZæøåÆØÅ ]*[a-zA-ZæøåÆØÅ]';
$('#item-type').selectize({
persist: true,
valueField: 'text',
labelField: 'text',
searchField: ['text'],
options: [
{id: '1', text: 'Apple'},
{id: '2', text: 'Banana'},
{id: '3', text: 'Orange'},
{id: '4', text: 'Cherry'},
],
createFilter: function(input) {
var match, regex;
regex = new RegExp('^' + REGEX + '$', 'i');
match = input.match(regex);
if (match) {
console.log(match[0]);
return !hasOwnPropertyCaseInsensitive(this.options, match[0]);
}
return false;
},
create: true
});
jsFiddle demo (http://jsfiddle.net/json/Lh3anheq/35/)
Okay, based on our discussion in the comments above, you want the selectize.js to return the id of the selected item, and also let users create unique items.
You are right about the id: you just need to replace the valueField: 'text' with valueField: 'id'.
Now we need to fix the decision making in your function hasOwnPropertyCaseInsensitive.
The first argument in this function is an object of objects. If you see the console output, for this.options of your selectize element, you will see roughly the following structure (valueField is already replaced with id here):
{
idOfItem1: {
id: idOfItem1,
text: textOfItem1
},
idOfItem2: ...
}
Here is what the web inspector prints out for console.log(this.options):
So, we can iterate over all objects and still have the display value in the field text, and this is exactly the string that we need to compare against the user input for uniqueness.
function hasOwnPropertyCaseInsensitive(options, userValue) {
var exists = false;
for (var option in options) {
if (options.hasOwnProperty(option)) {
if (options[option].text.toLowerCase() === userValue.toLowerCase()) {
exists = true;
break; // break from the loop when the match is found. return true works as well.
}
}
}
return exists;
}
Note! The id of an element created by a user will be the same as the display value. I.e. if I add a new element to the list, e.g. Test, it will look like this:
{
Test: {
id: "Test",
text: "Test"
}
}
Please see the jsFiddle demo (http://jsfiddle.net/json/Lh3anheq/35/) and let me know if I missed something.
Related
document_table_Settings : function ()
{
return{
rowsPerPage: 5,
showNavigation: 'auto',
showColumnToggles: false,
fields: [
{key: 'para',label: 'Para',sortable: false},
{key: 'desc', label: 'Description',sortable: false},
{
key: 'rowId', label: 'Delete',sortable: false, fn: function (rowId, object) {
var html = "<button name='Del' id=" + rowId + " class='btn btn-danger'>Delete</button>"
return new Spacebars.SafeString(html);
}
},
{
key: 'rowId', label: 'Edit',sortable: false, fn: function (rowId, object) {
var html = "<button name='edit' id=" + rowId + " class='btn btn-warning'>Edit</button>"
return new Spacebars.SafeString(html);
}
}
]
};
}
I want to show description entries having show more and show less feature .As the description is long enough. so after 100 character it shows button to toggle.
If I understand you correctly, you are trying to only show the first 100 characters of the 'Description' column in the Reactive Table and then add some mechanism so that the user can click or rollover to see the entire 'Description' text.
There are a few ways to achieve this and I have provided two options below (in order of simplicity).
For a low tech rollover option, truncate the text to only show the first 100 characters, add an ellipsis (...) to the end of your text, then use the title property in a span element to show the full text on rollover.
First you will need to define a 'truncate' Template helper (I would make this a global helper so that you can use anywhere in your app).
Template.registerHelper('truncate', function(strValue, length) {
var len = DEFAULT_TRUNCATE_LENGTH;
var truncatedString = strValue;
if (length && length instanceof Number) {
len = length;
}
if (strValue.length > len) {
truncatedString = strValue.substr(1, len) + "...";
}
return truncatedString;
});
Then create a new Template for the column.
<template name="field_description">
<span title="{{data.description}}">{{truncate data.description}}</span>
</template>
And finally, change your Reactive Table configuration to use a Template.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
For a slightly more complicated option, you can take a similar approach but add a clickable link that would show more or less detail. To get it to work you have to define a few Reactive Vars, define an event handler, and change your 'Description' Template accordingly. Here is a rough solution that should work.
Change your template like so.
<template name="field_description">
<span>{{truncatedDescription}}
{{#if showLink}}
{{linkState}}
{{/if}}
</span>
</template>
Then add the necessary logic to your field_description template (including an event handler).
import { Template } from 'meteor/templating';
import './field-description.html';
Template.field_descirption.onCreated(function() {
const MAX_LENGTH = 100;
this.description = new ReactiveVar(Template.currentData().description);
this.showMore = new ReactiveVar(true);
if (this.description.get().length > MAX_LENGTH) {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
}
this.showLink = () => {
return Template.currentData().description.length > MAX_LENGTH;
};
this.toggleTruncate = () => {
if (this.showMore.get()) {
this.description.set(Template.currentData().description);
this.showMore.set(false);
} else {
this.description.set(Template.currentData().description.substr(1, MAX_LENGTH));
this.showMore.set(true);
}
};
});
Template.field_descirption.helpers({
truncatedDescription: function() {
return Template.instance().description.get();
},
showLink: function() {
return Template.instance().showLink();
},
linkState: function() {
if (Template.instance().showMore.get()) {
return 'show more';
} else {
return 'show less';
}
};
});
Template.field_descirption.events({
'click .js-more-less'(event, instance) {
instance.toggleTruncate();
},
});
Lastly, make sure your Reactive Table config is still setup to use a Template for the field.
fields: [
...,
{ key: 'desc', label: 'Description', tmpl: Template.field_description }
...,
];
Note that the second option makes use of Meteor's Reactivity to solve the problem. Let me know if you need additional explanation on how the 2nd solution works.
That should do it!
I have the following Webix combo:
{
view: "combo",
label: 'Select the name',
labelWidth:130,
options: {
data:[
{ itemId:"120", itemName:"Name 1"},
{ itemId:"121", itemName:"Name 2"}
],
body: { template: '#itemName#' }
},
on:{
onChange:function(id){ alert(id) }
}
}
It looks just as needed, but how can I get itemId after selecting new item? I can only get the auto-generated ID
The same code in a snippet:
http://webix.com/snippet/3a431f1c
Thanks in advance!
You have to get the object of the combobox and then you can get data of the selected item with the help of its getItem() method as:
var obj = this.getPopup().getBody().getItem(newValue); //the object
var id = obj.itemId; //the desired id which is itemId in your code
Please check the snippet here.
I want to dynamically add an option to an optiongroup in Selectize.js. The API only has
addOption(data)
updateOption(value, data)
addOptionGroup(id, data)
without much help on what "data" is. I've seen the examples for adding an option but no mention of using optionGroups
$('#button-addoption').on('click', function() {
control.addOption({
id: 4,
title: 'Something New',
url: 'http://google.com'
});
Thanks
Data is the object passed to the optgroup rendering method.
And so, you can put anything in it.
$('#selectize').selectize({
...
optgroupField: 'mygroup',
render: {
optgroup_header: function(data, escape) {
return '<div class="optgroup-header">' + escape(data.a) + escape(data.b) '</div>';
}
},
...
});
And then, whenever you want, you can add groups and options in the selectize:
//add group
var optGroup = { a: 'fruit', b: ... };
$('#selectize')[0].selectize.addOptionGroup('0', optGroup);
//add option
var option = { value: 'abc', text: 'banana', mygroup: '1'};
$('#selectize')[0].selectize.addOption(option);
Of course, if you only want a label for the group, you can do this:
//code
...
render: {
optgroup_header: function(data, escape) {
return '<div class="optgroup-header">' + escape(data) + '</div>';
}
...
//code
$('#selectize')[0].selectize.addOptionGroup('1', 'meat');
You can see the API demo (search for 'Optgroups (programmatic)' in page).
My jquery skills are not excellent. What I usually do is take plugins then eventually figure out what I need to do.
What I am doing right now is that I have a slideshow script, and I want it so that when the next slide becomes active / visible, a function is run. What that function does is check for any ID that has a type of css (the type being set in an array), and then adding a class to it. The reason I'm doing it like this is because it needs to expand do different things depending on what the type is.
I have some general code, but I really don't know enough to figure out what I'm doing. Here is what I have thus far:
The function:
function doAnimate(index) {
var item = $animations[index];
for (var i = 0; i < item.length; i++) {
var obj = item[i];
if (obj['type'] = "css") {$(this).addClass('in');}
};
}
The Array:
var $animations = [
[{
ID: "s5-01",
type: "css"
}],
[{
ID: "s5-02",
type: "css"
}],
[{
ID: "s5-03",
type: "css"
}],
[{
ID: "s5-04",
type: "css"
}],
[{
ID: "#5-05",
type: "css"
}]
];
This is the slideshow script and where it should run:
carousel.owlCarousel({
... cut stuff out that isn't important
pagination:true,
afterMove: function(elem) {
doAnimate();
}
});
And here is how this particular one is set up in the HTML
<div class="slide" id="slide-05">
<img id="s5-01" src="img1.png" />
<img id="s5-02" src="img2.png" />
<img id="s5-03" src="img3.png" />
<img id="s5-04" src="img4.png" />
<img id="s5-05" src="img5.png" />
</div>
So I have it like that, and the browser console says: Uncaught TypeError: Cannot read property 'length' of undefined
I am basically kind of lost, and in need of some assistance to get this working. I'm sure it is something basic that I just can't see, or perhaps I have it set up all wrong.
Any advice would be greatly appreciated!
I believe it should be $('.animations:eq(index)') not $animations[index]
Since $animations is not a valid selector.
Check this http://api.jquery.com/eq-selector/ for more info
There are a couple of things you need to fix in order for this to work.
JavaScript Array
// Array
var $animations = [
[{
'id': "s5-01",
'type': "css"
}],
[{
'id': "s5-02",
'type': "css"
}],
[{
'id': "s5-03",
'type': "css"
}],
[{
'id': "s5-04",
'type': "css"
}],
[{
'id': "s5-05",
'type': "css"
}]
];
Then your function:
JavaScript Function
// Animate function
function doAnimate(index) {
// Check array
for (var i = 0, len = $animations.length; i < len; i++) {
// If Array ID & Type match element ID
if( $animations[i][0].id == index &&
$animations[i][0].type == 'css' ) {
// Add class
$('img#'+index).addClass('in');
}
}
}
For this to work you must pass to doAnimate() the element ID, check my jsFiddle example: http://jsfiddle.net/sfhbX/
Another solution using $.each()(http://api.jquery.com/jQuery.each/). I also simplified your array so that I can do this: $animations[01].id and get s5-02 instead of $animations[1][0].id to get same value.
Array:
var $animations = [
{
'id': "s5-01",
'type': "yyy"
},
{
'id': "s5-02",
'type': "css"
},
{
'id': "s5-03",
'type': "css"
},
{
'id': "s5-04",
'type': "css"
},
{
'id': "s5-05",
'type': "css"
}
]
Same HTML stuff. Here's the JavaScript:
function doAnimate(index) {
var item = $animations[index];
$.each($animations, function(idx, val) {
// console.log(val); //logs the value of val to console at index `idx`
//check the `id` val at index idx if equal to the passed item.id
if (val.id === item.id && val.type === 'css') {
$("#"+item.id).addClass('in');
return false;
}
//and just to test if not equal to `css`
if (val.id === item.id && val.type !== 'css') {
$("#"+item.id).addClass('yy');
return false;
}
});
}
Demo fiddle here: http://jsfiddle.net/KpmNL/
You need to change this line:
if (obj['type'] = "css") {$(this).addClass('in');}
There are two problems with that. The first is that your if condition uses = (assignment) rather than == or === (comparison). The second is that you're using this, but what this refers to isn't the element you want; you instead want to select based on the id property of obj, so:
if (obj['type'] === "css") {$('#' + obj.id).addClass('in');}
Then in this section of code:
carousel.owlCarousel({
// cut stuff out that isn't important
pagination:true,
afterMove: function(elem) {
doAnimate();
}
});
That call to doAnimate() needs to be changed to pass an index for an element in your $animations array.
I am running a weird problem when I try to set Grid Filter list dynamically.
Let me explain by my code snippets
I have a column with filter list is defined as
{
text : 'Client',
dataIndex : 'topAccount',
itemId : 'exTopAccount',
filter: {
type: 'list',
options:[]
}
}
I initialize list from store in 'viewready'
viewready: function(cmp,eOpts){
cmp.getHeaderCt().child('#exTopAccount').initialConfig.filter.options = clientsStore.collect('topAccount');
}
===> WORKS GOOD
Now, I have to build the new client store based on the records when user moves to next page. Therefore I build the store in the 'change' event of paging
listeners: {
'change' :function( toolbar, pageData, eOpts ) {
var store = Ext.StoreManager.get('ExceptionRecords');
clientsStore.removeAll(true);
store.each(function(record){
if(clientsStore.findRecord('topAccount',record.data.topAccount.trim()) == null ) {
clientsStore.add({topAccount: record.data.topAccount.trim()})
}
})
Ext.getCmp('exceptionGridContainer').view.refresh;
Ext.getCmp('exceptionGridContainer').view.getHeaderCt().doLayout;
console.log(clientsStore);
Ext.getCmp('exceptionGridContainer').view.getHeaderCt().child('#exTopAccount').initialConfig.filter.options = clientsStore.collect('topAccount');
}
}
I can now see the new data in clientsStore . But Grid filter list is not updated. still showing old data. I tried refresh,layout etc. Nothing helps
Any help will be appreciated
Thanks
Tharahan
Just changing the value of a property does not affect the component rendered or computed state. The menu is created when the list is first initialized. The first time you do that, it works because that's before the initialization, but the second time, that's too late.
If you can grab a reference to the instantiated ListFilter, I think you could force the recreation of the menu this way:
listFilter.menu = listFilter.createMenu({
options: [ ... ] // new options
// rest of the filter config
});
So, supposing you have a reference to your target grid, you could change the options for the column with dataIndex of "topAccount" by a call similar to this:
var listFilter = grid
.findFeature('filters') // access filters feature of the grid
.get('topAccount'); // access the filter for column
listFilter.menu = listFilter.createMenu({
options: [ ... ] // new options
// rest of the filter config
});
--- Edit ---
OK, complete example. Tested, working.
Ext.widget('grid', {
renderTo: Ext.getBody()
,height: 400
,features: [{
ftype: 'filters'
,local: true
}]
,columns: [{
dataIndex: 'a'
,text: 'Column A'
,filter: {
type: 'list'
,options: ['Foo', 'Bar']
}
},{
dataIndex: 'b'
,text: 'Column B'
},{
dataIndex: 'c'
,text: 'Column C'
}]
,store: {
fields: ['a', 'b', 'c']
,autoLoad: true
,proxy: {
type: 'memory'
,reader: 'array'
,data: [
['Foo', 1, 'Bar']
,['Bar', 2, 'Baz']
,['Baz', 1, 'Bar']
,['Bat', 2, 'Baz']
]
}
}
,tbar: [{
text: 'Change list options'
,handler: function() {
var grid = this.up('grid'),
// forget about getFeature, I read the doc and found something!
filterFeature = grid.filters,
colAFilter = filterFeature.getFilter('a');
// If the filter has never been used, it won't be available
if (!colAFilter) {
// someone commented that this is the way to initialize filter
filterFeature.view.headerCt.getMenu();
colAFilter = filterFeature.getFilter('a');
}
// ok, we've got the ref, now let's try to recreate the menu
colAFilter.menu = colAFilter.createMenu({
options: ['Baz', 'Bat']
});
}
}]
});
I was solving similar problem and answers to this question helped me a lot. Local List filter menu is in fact lazy loaded (only created when clicked) and I needed to set filter menu to be reloaded if the grid store has been reloaded with different data. Solved it by destroying of menu after each reload, so on next click menu is recreated:
var on_load = function() {
var grid_header = me.gridPanel.filters.view.headerCt
if (grid_header.menu) {
grid_header.menu.destroy();
grid_header.menu = null;
}
}