Convert jQuery to Prototype JS - javascript

I need to convert the following jQuery into Prototype JS.
jQuery("button.btn-transcript").click(function() {
tsTarget = jQuery(this).attr("data-target");
if (jQuery(this).hasClass("collapsed")) {
jQuery(tsTarget).show(200);
jQuery(this).removeClass("collapsed");
jQuery(this).attr("area-expanded","true");
} else {
jQuery(tsTarget).hide(200);
jQuery(this).addClass("collapsed");
jQuery(this).attr("area-expanded","false");
}
});
I gave it a try but I'm not too good with JS prototype. Am I heading in the right direction?
$("button.btn-transcript").on('click', 'button.btn-transcript', function(event, el)) {
transTarget = $(this).readAttribute("data-target");
function(event,el) {
if($(this).hasClassName("collapsed")) {
$("transTarget").show();
$(this).removeClassName("collapsed");
$(this).writeAttribute("area-expanded", "true");
} else {
$("transTarget").hide();
$(this).addClassName("collapsed");
$(this).writeAttribute("area-expanded", "false");
}
}

Try this:
$(document).on('click', 'button.btn-transcript', function(evt, elm) {
var tsTarget = $$(elm.readAttribute('data-target')).first();
elm.toggleClassName('collapsed');
tsTarget.toggle();
elm.writeAttribute('aria-expanded',
(elm.readAttribute('aria-expanded') == 'true' ? 'false' : 'true'));
});
It's not going to work 100% the same, because hide and show in Prototype (which are collapsed here into the one-liner toggle) are instantaneous. If you want the item to transition over 200ms the way you had written yours, you will need to use CSS transition effects.
If your button controls more than one item (if more than one element in the DOM matches what you have entered in the data-target attribute), then you would change this very slightly:
$(document).on('click', 'button.btn-transcript', function(evt, elm) {
var tsTargets = $$(elm.readAttribute('data-target'));
elm.toggleClassName('collapsed');
tsTargets.invoke('toggle');
elm.writeAttribute(
'aria-expanded',
(elm.readAttribute('aria-expanded') == 'true' ? 'false' : 'true')
);
});

Related

jQuery switching between more than two classes

I've already posted a question about jQuery toggle method here
But the problem is that even with the migrate plugin it does not work.
I want to write a script that will switch between five classes (0 -> 1 -> 2 -> 3 -> 4 -> 5).
Here is the part of the JS code I use:
$('div.priority#priority'+id).on('click', function() {
$(this).removeClass('priority').addClass('priority-low');
});
$('div.priority-low#priority'+id).on('click' ,function() {
$(this).removeClass('priority-low').addClass('priority-medium');
});
$('div.priority-medium#priority'+id).on('click', function() {
$(this).removeClass('priority-medium').addClass('priority-normal');
});
$('div.priority-normal#priority'+id).on('click', function() {
$(this).removeClass('priority-normal').addClass('priority-high');
});
$('div.priority-high'+id).on('click', function() {
$(this).removeClass('priority-high').addClass('priority-emergency');
});
$('div.priority-emergency'+id).on('click', function() {
$(this).removeClass('priority-emergency').addClass('priority-low');
});
This is not the first version of the code - I already tried some other things, like:
$('div.priority#priority'+id).toggle(function() {
$(this).attr('class', 'priority-low');
}, function() {
$(this).attr('class', 'priority-medium');
}, function() {
...)
But this time it only toggles between the first one and the last one elements.
This is where my project is: strasbourgmeetings.org/todo
The thing is that your code will hook your handlers to the elements with those classes when your code runs. The same handlers remain attached when you change the classes on the elements.
You can use a single handler and then check which class the element has when the click occurs:
$('div#priority'+id).on('click', function() {
var $this = $(this);
if ($this.hasClass('priority')) {
$this.removeClass('priority').addClass('priority-low');
}
else if (this.hasClass('priority-low')) {
$this.removeClass('priority-low').addClass('priority-medium');
}
else /* ...and so on... */
});
You can also do it with a map:
var nextPriorities = {
"priority": "priority-low",
"priority-low": "priority-medium",
//...and so on...
"priority-emergency": "priority"
};
$('div#priority'+id).on('click', function() {
var $this = $(this),
match = /\bpriority(?:-\w+)?\b/.exec(this.className),
current = match && match[0],
next = nextPriorities[current];
if (current) {
$this.removeClass(current).addClass(next || 'priority');
}
});
[edit: working demo]
Assuming you have 'priority' as the default class already on the element at the initialization phase, this will cycle through the others:
$('div#priority' + id)
.data('classes.cycle', [
'priority',
'priority-low',
'priority-medium',
'priority-normal',
'priority-high',
'priority-emergency'
])
.data('classes.current', 0)
.on('click', function () {
var $this = $(this),
cycle = $this.data('classes.cycle'),
current = $this.data('classes.current');
$this
.removeClass(cycle[current % cycle.length])
.data('classes.current', ++current)
.addClass(cycle[current % cycle.length]);
});
I have tried myself to do this with the sole help of toggleClass() and didn't succeeded.
Try my method that declares an array with your five classes and toggles dynamically through
them.Do adapt to your own names.
//variable for the classes array
var classes=["one","two","three","four","five"];
//add a counter data to your divs to have a counter for the array
$('div#priority').data("counter",0);
$(document).on('click','div#priority',function(){
var $this=$(this);
//the current counter that is stored
var count=$this.data("counter");
//remove the previous class if is there
if(($this).hasClass(classes[count-1])){
$(this).removeClass(classes[count-1]));
}
//check if we've reached the end of the array so to restart from the first class.
//Note:remove the comment on return statement if you want to see the default class applied.
if(count===classes.length){
$this.data("counter",0);
//return;//with return the next line is out of reach so class[0] wont be added
}
$(this).addClass(classes[count++]);
//udpate the counter data
$this.data("counter",count);
});
//If you use toggleClass() instead of addClass() you will toggle off your other classes.Hope is a good answer.

jQuery "on create" event for dynamically-created elements

I need to be able to dynamically create <select> element and turn it into jQuery .combobox(). This should be element creation event, as opposed to some "click" event in which case I could just use jQuery .on().
So does something like this exist?
$(document).on("create", "select", function() {
$(this).combobox();
}
I'm reluctant to use livequery, because it's very outdated.
UPDATE The mentioned select/combobox is loaded via ajax into a jQuery colorbox (modal window), thus the problem - I can only initiate combobox using colorbox onComplete, however on change of one combobox another select/combobox must be dynamically created, therefor I need a more generic way to detect creation of an element (selectin this case).
UPDATE2 To try and explain the problem further - I have select/combobox elements created recursively, there is also a lot of initiating code inside .combobox(), therefore if I used a classic approach, like in #bipen's answer, my code would inflate to insane levels. Hope this explains the problem better.
UPDATE3 Thanks everyone, I now understand that since deprecation of DOMNodeInserted there is a void left in DOM mutation and there is no solution to this problem. I'll just have to rethink my application.
You can on the DOMNodeInserted event to get an event for when it's added to the document by your code.
$('body').on('DOMNodeInserted', 'select', function () {
//$(this).combobox();
});
$('<select>').appendTo('body');
$('<select>').appendTo('body');
Fiddled here: http://jsfiddle.net/Codesleuth/qLAB2/3/
EDIT: after reading around I just need to double check DOMNodeInserted won't cause problems across browsers. This question from 2010 suggests IE doesn't support the event, so test it if you can.
See here: [link] Warning! the DOMNodeInserted event type is defined in this specification for reference and completeness, but this specification deprecates the use of this event type.
As mentioned in several other answers, mutation events have been deprecated, so you should use MutationObserver instead. Since nobody has given any details on that yet, here it goes...
Basic JavaScript API
The API for MutationObserver is fairly simple. It's not quite as simple as the mutation events, but it's still okay.
function callback(records) {
records.forEach(function (record) {
var list = record.addedNodes;
var i = list.length - 1;
for ( ; i > -1; i-- ) {
if (list[i].nodeName === 'SELECT') {
// Insert code here...
console.log(list[i]);
}
}
});
}
var observer = new MutationObserver(callback);
var targetNode = document.body;
observer.observe(targetNode, { childList: true, subtree: true });
<script>
// For testing
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
Let's break that down.
var observer = new MutationObserver(callback);
This creates the observer. The observer isn't watching anything yet; this is just where the event listener gets attached.
observer.observe(targetNode, { childList: true, subtree: true });
This makes the observer start up. The first argument is the node that the observer will watch for changes on. The second argument is the options for what to watch for.
childList means I want to watch for child elements being added or removed.
subtree is a modifier that extends childList to watch for changes anywhere in this element's subtree (otherwise, it would just look at changes directly within targetNode).
The other two main options besides childList are attributes and characterData, which mean about what they sound like. You must use one of those three.
function callback(records) {
records.forEach(function (record) {
Things get a little tricky inside the callback. The callback receives an array of MutationRecords. Each MutationRecord can describe several changes of one type (childList, attributes, or characterData). Since I only told the observer to watch for childList, I won't bother checking the type.
var list = record.addedNodes;
Right here I grab a NodeList of all the child nodes that were added. This will be empty for all the records where nodes aren't added (and there may be many such records).
From there on, I loop through the added nodes and find any that are <select> elements.
Nothing really complex here.
jQuery
...but you asked for jQuery. Fine.
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
This creates a new event called domNodeInserted, using the jQuery special events API. You can use it like so:
$(document).on("domNodeInserted", "select", function () {
$(this).combobox();
});
I would personally suggest looking for a class because some libraries will create select elements for testing purposes.
Naturally, you can also use .off("domNodeInserted", ...) or fine-tune the watching by passing in data like this:
$(document.body).on("domNodeInserted", "select.test", {
attributes: true,
subtree: false
}, function () {
$(this).combobox();
});
This would trigger checking for the appearance of a select.test element whenever attributes changed for elements directly inside the body.
You can see it live below or on jsFiddle.
(function($) {
$(document).on("domNodeInserted", "select", function() {
console.log(this);
//$(this).combobox();
});
})(jQuery);
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script>
// For testing
setTimeout(function() {
var $el = document.createElement('select');
document.body.appendChild($el);
}, 500);
</script>
<script>
(function($) {
var observers = [];
$.event.special.domNodeInserted = {
setup: function setup(data, namespaces) {
var observer = new MutationObserver(checkObservers);
observers.push([this, observer, []]);
},
teardown: function teardown(namespaces) {
var obs = getObserverData(this);
obs[1].disconnect();
observers = $.grep(observers, function(item) {
return item !== obs;
});
},
remove: function remove(handleObj) {
var obs = getObserverData(this);
obs[2] = obs[2].filter(function(event) {
return event[0] !== handleObj.selector && event[1] !== handleObj.handler;
});
},
add: function add(handleObj) {
var obs = getObserverData(this);
var opts = $.extend({}, {
childList: true,
subtree: true
}, handleObj.data);
obs[1].observe(this, opts);
obs[2].push([handleObj.selector, handleObj.handler]);
}
};
function getObserverData(element) {
var $el = $(element);
return $.grep(observers, function(item) {
return $el.is(item[0]);
})[0];
}
function checkObservers(records, observer) {
var obs = $.grep(observers, function(item) {
return item[1] === observer;
})[0];
var triggers = obs[2];
var changes = [];
records.forEach(function(record) {
if (record.type === 'attributes') {
if (changes.indexOf(record.target) === -1) {
changes.push(record.target);
}
return;
}
$(record.addedNodes).toArray().forEach(function(el) {
if (changes.indexOf(el) === -1) {
changes.push(el);
}
})
});
triggers.forEach(function checkTrigger(item) {
changes.forEach(function(el) {
var $el = $(el);
if ($el.is(item[0])) {
$el.trigger('domNodeInserted');
}
});
});
}
})(jQuery);
</script>
Note
This jQuery code is a fairly basic implementation. It does not trigger in cases where modifications elsewhere make your selector valid.
For example, suppose your selector is .test select and the document already has a <select>. Adding the class test to <body> will make the selector valid, but because I only check record.target and record.addedNodes, the event would not fire. The change has to happen to the element you wish to select itself.
This could be avoided by querying for the selector whenever mutations happen. I chose not to do that to avoid causing duplicate events for elements that had already been handled. Properly dealing with adjacent or general sibling combinators would make things even trickier.
For a more comprehensive solution, see https://github.com/pie6k/jquery.initialize, as mentioned in Damien Ó Ceallaigh's answer. However, the author of that library has announced that the library is old and suggests that you shouldn't use jQuery for this.
You can use DOMNodeInserted mutation event (no need delegation):
$('body').on('DOMNodeInserted', function(e) {
var target = e.target; //inserted element;
});
EDIT: Mutation events are deprecated, use mutation observer instead
Just came up with this solution that seems to solve all my ajax problems.
For on ready events I now use this:
function loaded(selector, callback){
//trigger after page load.
$(function () {
callback($(selector));
});
//trigger after page update eg ajax event or jquery insert.
$(document).on('DOMNodeInserted', selector, function () {
callback($(this));
});
}
loaded('.foo', function(el){
//some action
el.css('background', 'black');
});
And for normal trigger events I now use this:
$(document).on('click', '.foo', function () {
//some action
$(this).css('background', 'pink');
});
There is a plugin, adampietrasiak/jquery.initialize, which is based on MutationObserver that achieves this simply.
$.initialize(".some-element", function() {
$(this).css("color", "blue");
});
This could be done with DOM4 MutationObservers but will only work in Firefox 14+/Chrome 18+ (for now).
However there is an "epic hack" (author's words not mine!) that works in all browsers that support CSS3 animations which are: IE10, Firefox 5+, Chrome 3+, Opera 12, Android 2.0+, Safari 4+. See the demo from the blog. The hack is to use a CSS3 animation event with a given name that is observed and acted upon in JavaScript.
One way, which seems reliable (though tested only in Firefox and Chrome) is to use JavaScript to listen for the animationend (or its camelCased, and prefixed, sibling animationEnd) event, and apply a short-lived (in the demo 0.01 second) animation to the element-type you plan to add. This, of course, is not an onCreate event, but approximates (in compliant browsers) an onInsertion type of event; the following is a proof-of-concept:
$(document).on('webkitAnimationEnd animationend MSAnimationEnd oanimationend', function(e){
var eTarget = e.target;
console.log(eTarget.tagName.toLowerCase() + ' added to ' + eTarget.parentNode.tagName.toLowerCase());
$(eTarget).draggable(); // or whatever other method you'd prefer
});
With the following HTML:
<div class="wrapper">
<button class="add">add a div element</button>
</div>
And (abbreviated, prefixed-versions-removed though present in the Fiddle, below) CSS:
/* vendor-prefixed alternatives removed for brevity */
#keyframes added {
0% {
color: #fff;
}
}
div {
color: #000;
/* vendor-prefixed properties removed for brevity */
animation: added 0.01s linear;
animation-iteration-count: 1;
}
JS Fiddle demo.
Obviously the CSS can be adjusted to suit the placement of the relevant elements, as well as the selector used in the jQuery (it should really be as close to the point of insertion as possible).
Documentation of the event-names:
Mozilla | animationend
Microsoft | MSAnimationEnd
Opera | oanimationend
Webkit | webkitAnimationEnd
W3C | animationend
References:
caniuse.com summary of compatibility of CSS Animations.
CSS AnimationEvent Interface (W3C).
JavaScript animationend vendor-support.
For me binding to the body does not work. Binding to the document using jQuery.bind() does.
$(document).bind('DOMNodeInserted',function(e){
var target = e.target;
});
instead of...
$(".class").click( function() {
// do something
});
You can write...
$('body').on('click', '.class', function() {
// do something
});
I Think it's worth mentioning that in some cases, this would work:
$( document ).ajaxComplete(function() {
// Do Stuff
});
create a <select> with id , append it to document.. and call .combobox
var dynamicScript='<select id="selectid"><option value="1">...</option>.....</select>'
$('body').append(dynamicScript); //append this to the place your wanted.
$('#selectid').combobox(); //get the id and add .combobox();
this should do the trick.. you can hide the select if you want and after .combobox show it..or else use find..
$(document).find('select').combobox() //though this is not good performancewise
if you are using angularjs you can write your own directive. I had the same problem whith bootstrapSwitch. I have to call
$("[name='my-checkbox']").bootstrapSwitch();
in javascript but my html input object was not created at that time. So I write an own directive and create the input element with
<input type="checkbox" checkbox-switch>
In the directive I compile the element to get access via javascript an execute the jquery command (like your .combobox() command). Very important is to remove the attribute. Otherwise this directive will call itself and you have build a loop
app.directive("checkboxSwitch", function($compile) {
return {
link: function($scope, element) {
var input = element[0];
input.removeAttribute("checkbox-switch");
var inputCompiled = $compile(input)($scope.$parent);
inputCompiled.bootstrapSwitch();
}
}
});

jQuery UI Slider to find id on document ready?

I have the situation to prepopulate stored value from hidden element in jquery ui slider based on its id as like below,
jQuery(function(){
if(jQuery("input[name=color_overlay_nav_bar]").val() != ''){
jQuery(".slider_global_style_overlay ").slider({
create: function(event, ui) {
console.log(jQuery(this).attr('id'));
if(jQuery(this).attr('id') == 'nav_overlay_id'){
value:jQuery("input[name=color_overlay_nav_bar]").val();
}
}
});
}
});
Here input[name=color_overlay_nav_bar] has the opacity value, this needs to pre populate based on slider id.I have used Create event on document ready function to find id
But still i could not get it. something i missed here. What i done wrong on this.Kindly advice.
Thanks,
Dinesh
Sorry, my english is so bad. HEHEHE
You can't use "value" in "create" like this.
try this...
$(function(){
if($("input[name=color_overlay_nav_bar]").val() != '')
{
$(".slider_global_style_overlay ").slider
({
create: function(event, ui)
{
if($(this).attr('id') == 'nav_overlay_id'){
$(this).slider("value", $("input[name=color_overlay_nav_bar]").val());
}
}
})
}
});
OR
try this AFTER your the slide instance
if($("input[name=color_overlay_nav_bar]").val() != '')
{
$("#nav_overlay_id").slider("value", $("input[name=color_overlay_nav_bar]").val());
}

CKEditor hook/event fired up on switching to/from source view

I try to convert a div tag into something I can drag and drop in CKEditor (as asked in another question).
Do you know how I can trigger the event when someone switches between source view and WYSIWYG mode?
I think this is what you are looking for:
CKEDITOR.on('instanceCreated', function(e) {
e.editor.on('mode', function(e){
// Do your stuff here
alert(e.editor.mode);
});
});
If you mean, you want to capture source mode changes , then you could try something like this:
//add this to your CKeditor’s config.js
$('textarea.cke_source').live('keyup', function() {
$(this)
.closest('.cke_wrapper')
.parent()
.parent()
.prev()
.ckeditorGet()
.fire('change');
});
This discussion might help as well: ckEditor
Hope it helps
CKEditor onChange plugin:
Get a notification (new event) whenever the content of CKEditor changes.
http://ckeditor.com/addon/onchange
I think you should write a plugin to make a fake element for the wysiwyg-view.
Ckeditor is able to recognize elements that need to be replaced with fake-elements.
I made a start for you:
(function() {
CKEDITOR.plugins.add('myPlugin', {
requires : ['fakeobjects'],
init: function(editor) {
var me = this;
var pluginName = 'myPlugin';
editor.addCommand(pluginName, new CKEDITOR.dialogCommand(pluginName));
editor.addCss( // your custom css for your placeholder here
'div.myPluginElement' +
'{' +
'border: 1px solid #a9a9a9;' +
'width: 70px;' +
'height: 50px;' +
'}'
);
},
afterInit : function(editor) {
var dataProcessor = editor.dataProcessor,
dataFilter = dataProcessor && dataProcessor.dataFilter;
if (dataFilter) {
dataFilter.addRules({
elements : {
div : function(element) {
if (typeof element.attributes['class'] !== 'undefined' && element.attributes['class'].indexOf('myPluginElement') != -1)
return editor.createFakeParserElement(element, 'myPluginElement', 'div', false);
else return;
}
}
});
}
}
});
})();

jQuery ancestors using jQuery objects

I'd like to check ancestry using two jQuery objects. They don't have IDs, and are only going to be available as jQuery objects (or DOM nodes if you called get()). jQuery's is() only works with expressions, so this code would be ideal but will not work:
var someDiv = $('#div');
$('a').click(function() {
if ($(this).parents().is(someDiv)) {
alert('boo');
}
}
Just want to see if one element is a child of another and I'd like to avoid stepping back into DOM land if possible.
You can use the index() method to check if an element exists in a list, so would the following work?
var someDiv = $('#div');
$('a').click(function() {
if ($(this).parents().index(someDiv) >= 0) {
alert('boo');
}
}
From #index reference.
Checking for (this).parents().index(someDiv) >= 0, as #Gareth suggests, will work just fine.
However, using the jQuery ancestry plugin is way faster / more efficient.
Along those lines, parents() optionally accepts a selector itself:
$('a').click(function() {
if ($(this).parents("#div").length) {
alert('boo');
}
});
One way would be to use the filter function
$('a').click(function() {
$(this).parents().filter(function() {
return this == someDiv[0];
}).each(function() {
alert('foo');
})
}
I think you may also be able to get away with using jQuery.inArray
if ($.inArray( someDiv, $(this).parents() ) ) {
alert('boo');
}
Would you not get the result you want from simply using a CSS selector?
$( '#div a' ).click( function() { ... } );
Try this:
var someDiv = $('#div');
$('a').click(function() {
if ($.inArray($(this).parents().get(), someDiv.get(0)) {
alert('boo');
}
}
var $element = $('a');
while ($element && !$element.is('someDiv')) {
var $element = $element.parent();
};

Categories