Populating icons in Sencha Touch selectfield - javascript

I've got a pretty simple problem whose solution turns out not to be that simple at all.
I want to add images in front of each option of a selectfield. To be more accurate, I want to add images to the picker it triggers, and also to the selectfield's current value.
For the sake of simplicity, I'll create a little example:
Let's say, you want a user to choose between one of the four playing card suits Diamonds, Hearts, Spades and Clubs. To support visual recognition, you want to prepend the corresponding symbol to each suit name, so it could look something like this:
My first choice of a sencha touch component, that enables selecting from a given set of options naturally was selectfield. Unfortunately, it only seems to be able to display pure text, and nothing more. After digging into the sencha touch sources, I finally came up with half a solution. Basically, I pass the selectfield a custom defaultPhonePickerConfig, in which the corresponding picker(that is used by the selectfield to display the options) gets assigned a custom itemTpl. The itemTpl does the rest, namely adding some html to display the image:
defaultPhonePickerConfig: {
listeners: {
initialize: function() {
var slots = this.query('pickerslot');
Ext.each(slots, function(slot) {
slot.setItemTpl('<img src="someImage.jpg"> {text}');
});
},
change: function() {
// reconstruct the selectfield's change handler,
// since it gets overwritten
var iconSelect = Ext.Viewport.query('#IconSelect')[0];
iconSelect.onPickerChange.apply(iconSelect, arguments);
}
}
}
A working fiddle for this solution can be found here.
My solution isn't that bad, but there's a slight cosmetical problem, that's just not acceptable to me: The icons are only displayed in the picker (lower part of the screenshot above), but not the selectfield itself (upper, grayed out part) when the option was selected. And there seems to be no good way to add an icon to the selectfield's current value aswell.
And that's the main concern of my question: What good way is there to add an icon to both the picker's options and also to the selecfield's current value? Do I maybe just have to add relatively little code to my existing solution or should I take a whole nother approach?
Every contribution is appreciated. Thank you!
Update:
After some hacking around, I found a way (an ugly one) to prepend an icon to the selectfield itself. It is mostly based on brutal HTML DOM manipulation: I now also define event handlers for the selectfield itself (change and painted). On initialization and every time the value is changed, my handlers search the generated DOM for the underlying <input> and mess around with it. (That bad boy is probably the reason why we can't assign HTML in the first place, since the framework changes its value attribute. And value on the other hand can only contain plain text.)
This is how I define the selectfield's listeners:
listeners: {
change: function () {
var pickerDOM = document.querySelector('#' + this.getId() + ' input[name="picker"]');
PickerIcons.app.prependIconToSelectfield(arguments[1], pickerDOM);
},
painted: function () {
// Initialize an icon on creation
var pickerDOM = document.querySelector('#' + this.getId() + ' input[name="picker"]');
PickerIcons.app.prependIconToSelectfield(this.getValue(), pickerDOM);
}
}
The corresponding function prependIconToSelectfield() just defines some CSS:
prependIconToSelectfield: function (optValue, domElement) {
var iconUrl = this.getIconUrl(optValue);
domElement.style.backgroundImage = 'url(' + iconUrl + ')';
domElement.style.backgroundSize = '20px 20px';
domElement.style.backgroundRepeat = 'no-repeat';
domElement.style.backgroundPosition = 'left center';
domElement.style.paddingLeft = '30px';
}
Check out this fiddle for a working example.
This is still no good solution to me, since doing this kind of hackish DOM manipulation is way too rogue for my taste. I don't know what the side effects of actively messing around in the DOM could be in bigger projects, and don't want to learn it the hard way. So, I'm still looking for a cleaner solution.

First kudos on working so hard sencha touch is extremely hard to manipulate when you try to do something out of the box. Having said that let me try & propose a solution for what you want.
A selectfield in sencha has the following DOM tag structure.
div.x-field-select
div.x-field-input
input.x-input-el
div.x-clear-icon
div.x-field-mask
Now concentrate on the x-clear-icon it is normally hidden since a selectfield does not need a clear button. First write a css class for it to show it(display: block). This would display it with an X button similar to text field & it will be positioned towards the right corner. You can through css position it to the left and on change of the select field you can change its background to what you want. It is not a very straight forward solution but i have tried it for a similar problem & it works. Judging from what you have done above i think you can do it. All the best.
Hope my solution helps.

Related

How do I use Javascript to delay visibility of a text box and then hide another box after the first becomes visible

Im very new to this and have reviewed other posts similar to this question. However, I'm finding that those solutions don't work for me.
Background: I'm working in Wix's Velo platform for Javascript. (forgive me if that's not the right technical terminology here)
My goal: When my website home page loads, I want one of the text boxes on the page (#text45) to NOT be visible until 5 seconds have passed. Then, when box #text45 is visible, I want another plain box (#box2) to turn to hidden.
I have found some examples like the one below: (not all code has been pasted and I realize some elements like div1 would need to change to my specific element names)
document.getElementById("div1").style.visibility = "visible";
}
setTimeout("showIt()", 5000);
However, I get an error code: Cannot find name 'document'. Do you need to change your target library? Try changing the 'lib' compiler option to include 'dom'.
When researching this, I found out that Velo cannot access the dom and elements can only be accessed via "$w".
Would someone be kind enough to set me in the right direction on how to accomplish the "goal" above? I would really appreciate it! Thank you in advance.
Here's how you would do it. Note, that it's good practice to change the IDs of your elements to more descriptive names, but I've stuck with the names you provided in your question.
Start by setting #text45 to hidden in using the Properties & Events panel.
Then use this code (note that your page might already have an onReady. If it's there an you're not using it yet, delete all the code on the page and replace it with this):
$w.onReady( () => {
setTimeout(() => {
$w('#text45').show();
$w('#box2').hide();
}, 5000)
} );

How can I position an Angular Material panel dialog relative to a button?

According to a discussion at Github one cannot position a standard dialog (api), but panel dialogs (api) can be positioned.
A simplified demo shows that this is true:
var position = this._mdPanel.newPanelPosition().bottom(0).right(0);
The Angular Material docs show a method that allows positioning relative to the clicked element (or whatever is passed in). I'm unable to get this to work, however.
var target = el.target;
var position = this._mdPanel.newPanelPosition().relativeTo(target);
Passing in hard values for .top() and .right(), for example, allows positioning relative to the viewport. I can't get positioning relative to the clicked element, though. How is this supposed to work?
I've been working with Angular Material for the past several months and still find the documentation lacking, so forgive the length of this post as my pseudo documentation on the issue. But here is what I do know:
I've only been able to get the panel location to work, relative to a target element, by chaining the addPanelPosition function onto the relativeTo function as such:
var position = this._mdPanel
.newPanelPosition()
.relativeTo(ev.target)
.addPanelPosition('align-start', 'below') // or other values
(in this case, ev is the $event object passed by ng-click)
I was able to track down the acceptable parameters for addPanelPosition and they are the following:
Panel y position only accepts the following values:
center | align-tops | align-bottoms | above | below
Panel x Position only accepts the following values:
center | align-start | align-end | offset-start | offset-end
Interstingly enough, in the Angular Material demo, they use the this._mdPanel.xPosition.ALIGN_START and this._mdPanel.yPosition.BELOW properties which simply resolve to strings as their x and y values for the addPanelPosition function. I've always gone straight with the string values. However, using string values could be problematic if the development of this feature is still in flux and they change the acceptable string values.
I'll point out one more issue I've seen.
Another trick they use in the demo is to specify a class name in the relativeTo function instead of a target element, then place that class on the target element itself. The reason this approach can be helpful is because the $event object from ng-click can provide different target elements based on what exactly was clicked. For example, clicking the button <div> is going to give a different target than clicking the <span> text inside the button. This wil cause your panel to shift locations unless you provide the additional functionality not to do so.
Codepen
I took their demo and really cut it down to size to focus on this issue. You can see the updated codepen here
As I post in a comment, here you can see it working on a plunker.
My solution is very close the to #I think I can code answer. However, in my answer, instead of a menu, a <md-dialog> is displayed when the button is clicked, as it's requested in the OP.
Besides the working plunker with a dialog, there is no much to add to the good #I think I can code answer. As it's shown in the angular-material md-panel demo, the key here is to set the position of the panel relative to the button. To do that (like in the angular-material demo), we can use a specific css class (demo-dialog-open-button in my example) to find the target element. this is a tricky thing in my opinion...but it works well for this use case (it's also well explained in the other answer).
Code for reference, see the plunker for the complete details:
html (note the css class added to the button):
<md-button class="md-primary md-raised demo-dialog-open-button" ng-click="ctrl.showDialog($event)">
Dialog
</md-button>
JS controller.
var position = this._mdPanel.newPanelPosition()
.relativeTo('.demo-dialog-open-button')
.addPanelPosition(this._mdPanel.xPosition.ALIGN_START, this._mdPanel.yPosition.BELOW);
Hope it helps
Dialogs are very simple widgets. Trapping focus is about the most complicated thing they do. It pains me that your issue has evolved into such a complex one.
Just to state the obvious, you do have complete control over positioning any individual dialog thanks to your configured class name.
.demo-dialog-example {
top: 20px;
left: 20px;
}
Also, in your showDialog method, why not set up a call-back via a promise for the open method? Something like:
this._mdPanel.open(config).then(function() {
var dialog = angular.element(document.querySelector('.demo-dialog-example'));
//Centering, positioning relative to target, or draggable logic goes here
});
I respect that you are trying to improve the logic of the plugin and do things the "Angular way", but these relatively simple requirements should not be causing you this much heartache.

Listen to specific changes on contenteditable?

Warning: not duplicate with existing questions, read through
I know I can have an event listen on changes on an contenteditable element.
What I would like is to be able to know what the changes are.
For example:
inserted "This is a sentence." at position X.
deleted from position X to Y.
formatted from X to Y with <strong>
Is that possible? (other than by doing a diff I mean)
The reason for this is to make a WYSIWYG editor of other languages than HTML, for example Markdown.
So I'd like to apply the changes to the Markdown source (instead of having to go from HTML to Markdown).
You may be able to do something with MutationObservers (falling back to DOM Mutation events in older browsers, although IE <= 8 supports neither) but I suspect it will still be hard work to achieve what you want.
Here's a simple example using MutationObservers:
http://jsfiddle.net/timdown/4n2Gz/
Sorry, but there is no way to find out what the changes are without doing a diff between the original content and the modified one when changes occur.
Are you looking for this
var strong=document.createElement("strong");
var range=window.getSelection().toString().getRangeAt(0);
range.surroundContents(strong);
this was for third part
You just need to select what you want to surround using real User interaction.
If you wanna do it dynamically
var range=document.createRange();
range.setStart(parentNode[textNode],index to start[X])
range.setEnd(parentNode[textNode],index to end[Y])
range.surroundContents(strong);
For 2nd Part
range.deleteContents()
1st part can be done by using simple iteration
var textnode=// node of the Element you are working with
textnode.splitText(offset)
offset- position about which text node splitting takes place[here==X]
Two child Nodes have been created of the parent editable Element
Now use simple insertBefore() on parent editable Element Node.
hope you will find it useful
The API you're looking for does not exist, as DOM nodes do not store their previous states.
The data / events you're wishing to get back are not native implementations in any browser Ive come across, and I struggle to think of a datatype that would be able to generically handle all those cases. perhaps something like this:
function getChanges() {
/* do stuff here to analyse changes */
var change = {
changeType : 'contentAdded',
changeStart : 50, /* beginning character */
changeContent : 'This is a sentence'
}
return change;
}
Since you're trying to get custom events / data, you're probably going to need a custom module or micro-library. Either way, to look at the changes of something, you need somehow be aware of what has changed, which can only be done by comparing what it was to what it is now.

JavaScript seem just to react after MouseMove

probably the strangst thing I've ever seen: I want to load a grid after the document is loaded. Since the grid (infragistics) seems to delay, I set a interval, in the interval I try to get the needed element, if I cant find it, I wait another 500 ms:
function trySetEditMode(obj) {
var testObj = $('#' + obj.btnId).parents("tr[type='row']").get(0);
if (testObj && testObj._object){
clearInterval(_intervalId);
The funny thing is: Even if I see the with the IE debugging tool the row is there, it justdoenst find it. It works just when I move arround with the mouse. This effect seems totally random.
Has anyone an idea how this is possible? I tried to tinker arorund with .focus, .blur etc, but nothing seems to work.
btw: obj is a custom object from me, the button id is the clientid of the button and is there. For example:
$('#' + obj.btnId).parents('tr').length
returns a length.
Can this be a bug from the infragistics control? If yes, how could I simulate this mouseMove?
Best regards
Matthias
Creating the row objects is costly on the client and as such they are created on demand to avoid a large performance hit if all row objects were created initially. The actual creation of these objects is on mouse over or when you use the get_row() method that is provided as part of the client side object model. As such the recommended approach is to use the client side object model to get a reference to the row, for example the following will get the first row:
var grid = $find("webDataGrid1");
var row = grid.get_rows().get_row(0);
probably someoneelse will have this problem as well with infragistics webdatagrid:
Sidenote: I cant believe the ig support couldnt tell me that:
The Grid is kinda lazyLoaded, the dom gets loaded but the ig relevant attributes AND the object behind just get loaded when you go with the mouse over. So it didnt matter how long I waited, the object wasnt just there.
Since I had no time to get a proper solution, I basically fired manually the mouse over event on a cell. It doesnt matter on what you do this, but I tested it with the first one and it worked proper.
//The grid doesnt load data (except the first row) until the mouse did hover over it
//Get a cell, and hover over it
var parentTd = btn.parents('td').first();
var cell = parentTd.siblings()[0];
var rows = $find('dgrRoles').get_rows();
if (cell && rows) {
cell.target = cell;
rows._onMouseOver(cell);
}

jQuery Graceful Degradation

I want to spruce up some areas of my website with a few jQuery animations here and there, and I'm looking to replace my AJAX code entirely since my existing code is having some cross-browser compatibility issues. However, since jQuery is a JavaScript library, I'm worried about my pages not functioning correctly when JavaScript is turned off or doesn't exist in a user's browser.
I'll give an example: Currently, I'm using a pure CSS tooltip to give my users (players, the site is a browser game) information on other users. For example, if the other players in the game satisfy one or more conditions, a target icon is displayed next to their name, and upon hovering over that target icon information regarding the reasons behind the target is displayed. This is useful information, as it helps my players to know who they should plan to attack next in the game.
Currently, I do such tooltips using CSS. I have a parent div that holds the image of the target icon of class "info". I then have a div inside of that with class "tooltip" that, on the hover state of the "info" class that it is contained in, is shown, but on the normal state is hidden. I thought it was rather clever when I read about it, and since no JavaScript is used it works on any CSS compliant browser.
I would like to use jQuery to achieve the same effect, mostly because it would look much cleaner, but also because I believe quick and subtle animations can make such things "randomly appearing" make a lot more sense to the user, especially on the first encounter. I'm just wondering if the two will conflict. This is only one example of this, there are numerous other examples where the inability to use JavaScript would hinder the site.
So what I'm asking I guess is, how does one make a jQuery site degrade gracefully on browsers that do not support JavaScript, but otherwise do support most CSS? My goal is for the site to function on a basic level for all users, regardless of choice in browser. The animation is a good example, but I'm also worried about the more dynamic bits, like the auto-updating with AJAX, etc. Are there any good resources on how to achieve this, or do you have any advice about the best way such degradability could be achieved?
Thanks
PS: Totally irrelevant, but Firefox seems to think that "degradability" isn't a word, but "biodegradability" (with the "bio" prefix) is. Weird...
If you consider the "Cascading Order" of css, could you not just add a css style at the very end of all your previous css definition in order to cancel any css effect you currently have for tooltip effect ?
That css rule would only be declared if Javascript is activated and JQuery detected.
That way, you are sure your css tooltip effect is not in conflict with your JQuery effect.
Something like:
a.info:hover span{ display:none}
with the use of "js_enabled" class to make this css rule conditional.
You also can do it by adding css rule on the fly:
function createCSSRule(rule,attributes)
{
//Create the CSS rule
var newRule = "\n"+rule+"{\n";
for (var attribute in attributes)
{
newRule += "\t" + attribute + ": " + attributes[attribute] + ";\n";
}
newRule += "}\n";
//Inject it in the style element or create a new one if it doesn't exist
styleTag = $E('style[type="text/css"]') || new Element("style").setProperty('type','text/css').injectInside(document.head);
if(window.ie)
{
styleTag.styleSheet.cssText += newRule;
}
else
{
styleTag.appendText(newRule);
}
}
The most simple solution for Separation of CSS and Javascrip is to remove your css class
function jscss(a,o,c1,c2)
{
switch (a){
case 'swap':
o.className=!jscss('check',o,c1)?o.className.replace(c2,c1): <-
o.className.replace(c1,c2);
break;
case 'add':
if(!jscss('check',o,c1)){o.className+=o.className?' '+c1:c1;}
break;
case 'remove':
var rep=o.className.match(' '+c1)?' '+c1:c1;
o.className=o.className.replace(rep,'');
break;
case 'check':
return new RegExp('\\b'+c1+'\\b').test(o.className)
break;
}
}
This example function takes four parameters:
a
defines the action you want the function to perform.
o
the object in question.
c1
the name of the first class
c2
the name of the second class
Possible actions are:
swap
replaces class c1 with class c2 in object o.
add
adds class c1 to the object o.
remove
removes class c1 from the object o.
check
test if class c1 is already applied to object o and returns true or false.
If something can be done completely in CSS I say keep it that way. If lack of javascript in the browser is a concern, then most of the time I show the entire page unaffected.
Say for instance I'm going to use jQuery to toggle an element when a checkbox is clicked. On page load I look at the checkbox and update the element accordingly. If javascript is not enabled the element will still appear and the site will still be usable. Just not as nice.
Man, you have a browser-based game, right? You have less than 1% users with JS disabled! And that 1% is the apocalyptic number because I can BET that you have less than that ;)
Anyhow, if you are really concerned about this, just do the site without any JavaScript. And make it functional 100%. After your site works completely without any JS flavour, just start to improve with jQuery (or any other library; jQuery is the best :P ). But with careful: do not change ANY of you HTML. It's easier than it looks ;)
And yes, if you have things that work without JS (like those tooltips) keep it!

Categories

Resources