javascript change event fires when page loads - how to avoid this? - javascript

I have a form with chained select boxes. The zend controller sets the default values for these select boxes (values come from the db). I am a jquery novice.
$form->setDefaults($data);
a jquery file is loaded :
$(document).ready(function(){
// set up the chained select
$("#region").remoteChained("#country", "/ws/regionstructure");
$("#province").remoteChained("#region", "/ws/regionstructure");
$("#town").remoteChained("#province", "/ws/regionstructure");
});
The problem is that when the page loads it is triggering the change event for country and resetting all of the selects.
Here is the remoteChained jquery code that is being called:
/*
* Remote Chained - jQuery AJAX(J) chained selects plugin
*
* Copyright (c) 2010-2011 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
*/
(function($) {
$.fn.remoteChained = function(parent_selector, url, options) {
return this.each(function() {
/* Save this to self because this changes when scope changes. */
var self = this;
var backup = $(self).clone();
/* Handles maximum two parents now. */
$(parent_selector).each(function() {
$(this).bind("change", function() {
/* Build data array from parents values. */
var data = {};
$(parent_selector).each(function() {
var id = $(this).attr("id");
var value = $(":selected", this).val();
data[id] = value;
});
$.getJSON(url, data, function(json) {
/* Clear the select. */
$("option", self).remove();
/* Add new options from json. */
for (var key in json) {
if (!json.hasOwnProperty(key)) {
continue;
}
/* This sets the default selected. */
if ("selected" == key) {
continue;
}
var option = $("<option />").val(key).append(json[key]);
$(self).append(option);
}
/* Loop option again to set selected. IE needed this... */
$(self).children().each(function() {
if ($(this).val() == json["selected"]) {
$(this).attr("selected", "selected");
}
});
/* If we have only the default value disable select. */
if (1 == $("option", self).size() && $(self).val() === "") {
$(self).attr("disabled", "disabled");
} else {
$(self).removeAttr("disabled");
}
/* Force updating the children. */
$(self).trigger("change");
});
});
/* Force updating the children. */
$(this).trigger("change");
});
});
};
/* Alias for those who like to use more English like syntax. */
$.fn.remoteChainedTo = $.fn.remoteChained;
})(jQuery);

I don't see any issue with removing the change event, but the way that plugin works seems highly inefficient. I refactored this (untested), so give this a go and see how it works for you.
You can call it like so:
$( "#region" ).remoteChained( { parentSelector: "#country", url: "/ws/regionstructure" } );
$( "#province" ).remoteChained( { parentSelector: "#region", url: "/ws/regionstructure" } );
$( "#town" ).remoteChained( { parentSelector: "#province", url: "/ws/regionstructure" } );
This shouldn't trigger the change event. If you find you need to do this, you can do something like this:
$( '#region' ).trigger( 'change' );
Here is the new plugin code. Let me know if you have any issues with it:
( function( $ ) {
$.fn.remoteChained = function ( options ) {
var defaults = { },
settings = $.extend( { }, defaults, options );
return this.each( function () {
var el = $( this ),
parent = $(settings.parentSelector),
data = { };
parent.each( function () {
var el = $(this);
data[ el.attr( 'id' ) ] = el.find( ':selected' ).val();
});
parentSelector.bind( 'change.remotechanged', function ( e ) {
var request = $.ajax( { url: settings.url, data: data } );
request.done( function ( response ) {
var newOption;
el.find( 'option' ).remove();
for ( var key in response ) {
if ( !response.hasOwnProperty( key ) ) {
continue;
}
newOption = $( '<option />' )
.val( key )
.append( response[ key ] );
key === 'selected' && newOption.attr( 'selected', 'selected' );
newOption.appendTo( el );
}
if ( el.find( 'option' ).length === 1 && el.val() === '' ) {
el.attr( 'disabled', 'disabled' );
} else {
el.removeAttr( 'disabled' );
}
});
});
});
};
})( jQuery );

Related

CKEditor Character Count (charcount) is not working

If anyone has any experience with adding a charcount (Characters count) plugin for CKEditor, I sure could use a hand. This is my understanding of what needs to be done to add a plugin to CKEditor to count characters.
Create a plugin.js file which has the JavaScript to count the characters, and place this file at /web/server/root/ckeditor/plugins/charcount/plugin.js
Add the line config.extraPlugin = 'charcount'; to the config file at /web/server/root/ckeditor/config.js
Style the Character Count by adding CSS to the file at /web/server/root/ckeditor/skin/skin_name/editor.css.
Here is the plugin.js file I am using. Note: This JavaScript comes from the first post from this thread on the CKEditor forum.
CKEDITOR.plugins.add( 'charcount',
{
init : function( editor )
{
var defaultLimit = 'unlimited';
var defaultFormat = '<span class="cke_charcount_count">%count%</span> of <span class="cke_charcount_limit">%limit%</span> characters';
var limit = defaultLimit;
var format = defaultFormat;
var intervalId;
var lastCount = 0;
var limitReachedNotified = false;
var limitRestoredNotified = false;
if ( true )
{
function counterId( editor )
{
return 'cke_charcount_' + editor.name;
}
function counterElement( editor )
{
return document.getElementById( counterId(editor) );
}
function updateCounter( editor )
{
var count = editor.getData().length;
if( count == lastCount ){
return true;
} else {
lastCount = count;
}
if( !limitReachedNotified && count > limit ){
limitReached( editor );
} else if( !limitRestoredNotified && count < limit ){
limitRestored( editor );
}
var html = format.replace('%count%', count).replace('%limit%', limit);
counterElement(editor).innerHTML = html;
}
function limitReached( editor )
{
limitReachedNotified = true;
limitRestoredNotified = false;
editor.setUiColor( '#FFC4C4' );
}
function limitRestored( editor )
{
limitRestoredNotified = true;
limitReachedNotified = false;
editor.setUiColor( '#C4C4C4' );
}
editor.on( 'themeSpace', function( event )
{
if ( event.data.space == 'bottom' )
{
event.data.html += '<div id="'+counterId(event.editor)+'" class="cke_charcount"' +
' title="' + CKEDITOR.tools.htmlEncode( 'Character Counter' ) + '"' +
'> </div>';
}
}, editor, null, 100 );
editor.on( 'instanceReady', function( event )
{
if( editor.config.charcount_limit != undefined )
{
limit = editor.config.charcount_limit;
}
if( editor.config.charcount_format != undefined )
{
format = editor.config.charcount_format;
}
}, editor, null, 100 );
editor.on( 'dataReady', function( event )
{
var count = event.editor.getData().length;
if( count > limit ){
limitReached( editor );
}
updateCounter(event.editor);
}, editor, null, 100 );
editor.on( 'key', function( event )
{
//updateCounter(event.editor);
}, editor, null, 100 );
editor.on( 'focus', function( event )
{
editorHasFocus = true;
intervalId = window.setInterval(function (editor) {
updateCounter(editor)
}, 1000, event.editor);
}, editor, null, 100 );
editor.on( 'blur', function( event )
{
editorHasFocus = false;
if( intervalId )
clearInterval(intervalId);
}, editor, null, 100 );
}
}
});
Here is my config.js file. Notice I do have config.extraPlugin = 'charcount'; near the beginning of this file.
CKEDITOR.editorConfig = function( config ) {
config.extraPlugin = 'charcount';
config.toolbar = 'MyToolbar';
config.toolbar_MyToolbar =
[
{ name: 'document', items : [ 'Source','Save' ] },
{ name: 'editing', items : [ 'Find','Replace','Scayt', 'TextColor' ] },
{ name: 'insert', items : [ 'Image','CodeSnippet','Table','HorizontalRule' ] },
{ name: 'basicstyles', items : [ 'Bold','Italic','Strike','-','RemoveFormat' ] },
{ name: 'paragraph', items : [ 'NumberedList','BulletedList','-','Outdent','Indent','-','Blockquote' ] },
{ name: 'links', items : [ 'Link','Unlink','Anchor' ] },
{ name: 'tools', items : [ 'Maximize','ShowBlocks','-' ] }
];
};
I added the following to the bottom of my editor.css file.
.cke_skin_kama .cke_charcount {
display:block;
float:right;
margin-top:5px;
margin-right:3px;
color:#60676A;
}
.cke_charcount span.cke_charcount_count,
.cke_charcount span.cke_charcount_limit {
font-style: italic;
}
Here is my stage.html file.
<script src="ckeditor/ckeditor.js"></script>
<textarea id="editor1"></textarea>
<script> CKEDITOR.replace('editor1'); </script>
Here is a screen shot of the expected outcome, where the bottom right hand corner of my CKEditor should have text which counts the characters.
Here is a screen shot showing that my CKEditor does not have a character count.
There are some other similar threads on this. However, the similar threads add a button to the CKEditor toolbar. The JavaScript I am using does not add a button to the toolbar, and instead displays text at the bottom right-hand corner of CKEditor, which is what I am attempting to achieve. Hopefully this difference makes this post unique enough to not be considered a duplicate.
If it helps, my Web Server OS is Linux Mint version 17.2.
I was not able to figure out what the issue is with the JavaScript I posted in my original question. However, I did come across a solution to this issue, which I want to share here in case others happen upon this post with a similar issue. I found a plugin for CKEditor called the Word Count & Char Count plugin. This plugin can be downloaded from http://ckeditor.com/addon/wordcount or https://github.com/w8tcha/CKEditor-WordCount-Plugin. This plugin totally works, and display the number of words, paragraphs and/or characters in CKEditor.

How to override a Bootstrap plugin definition

I want to make some adjustments (actually, fixes) to the Bootstrap 3 Button plugin, but I can't wait for the whole submit code/pull request/approval process. I also don't want to make any edits directly to the bootstrap.js file. I thought I could add another file called bootstrap-override.js which loads after bootstrap.js and contained another copy of the Buttons plugin code with my customizations, but this isn't working. The code from the original Buttons plugin fires.
What is the correct pattern for replacing an already defined plugin like this:
/* ========================================================================
* Bootstrap: button.js v3.3.0
* http://getbootstrap.com/javascript/#buttons
* ========================================================================
* Copyright 2011-2014 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
* ======================================================================== */
+function ($) {
'use strict';
// BUTTON PUBLIC CLASS DEFINITION
// ==============================
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, Button.DEFAULTS, options)
this.isLoading = false
}
Button.VERSION = '3.3.0'
Button.DEFAULTS = {
loadingText: 'loading...'
}
Button.prototype.setState = function (state) {
var d = 'disabled'
var $el = this.$element
var val = $el.is('input') ? 'val' : 'html'
var data = $el.data()
state = state + 'Text'
if (data.resetText == null) $el.data('resetText', $el[val]())
// push to event loop to allow forms to submit
setTimeout($.proxy(function () {
$el[val](data[state] == null ? this.options[state] : data[state])
if (state == 'loadingText') {
this.isLoading = true
$el.addClass(d).attr(d, d)
} else if (this.isLoading) {
this.isLoading = false
$el.removeClass(d).removeAttr(d)
}
}, this), 0)
}
Button.prototype.toggle = function () {
var changed = true
var $parent = this.$element.closest('[data-toggle="buttons"]')
if ($parent.length) {
var $input = this.$element.find('input')
if ($input.prop('type') == 'radio') {
if ($input.prop('checked') && this.$element.hasClass('active')) changed = false
else $parent.find('.active').removeClass('active')
}
if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change')
} else {
this.$element.attr('aria-pressed', !this.$element.hasClass('active'))
}
if (changed) this.$element.toggleClass('active')
}
// BUTTON PLUGIN DEFINITION
// ========================
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.button')
var options = typeof option == 'object' && option
if (!data) $this.data('bs.button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
var old = $.fn.button
$.fn.button = Plugin
$.fn.button.Constructor = Button
// BUTTON NO CONFLICT
// ==================
$.fn.button.noConflict = function () {
$.fn.button = old
return this
}
// BUTTON DATA-API
// ===============
$(document)
.on('click.bs.button.data-api', '[data-toggle^="button"]', function (e) {
var $btn = $(e.target)
if (!$btn.hasClass('btn')) $btn = $btn.closest('.btn')
Plugin.call($btn, 'toggle')
e.preventDefault()
})
.on('focus.bs.button.data-api blur.bs.button.data-api', '[data-toggle^="button"]', function (e) {
$(e.target).closest('.btn').toggleClass('focus', e.type == 'focus')
})
}(jQuery);
You need to remove the original Bootstrap button plugin's event handlers using jQuery.off(). You need to .off() each of the original button plugin's .on()s. Example:
$('document').off('click.bs.button.data-api focus.bs.button.data-api blur.bs.button.data-api');

How to combine 2 isotope filter javascript functions?

Hi I have successfully setup isotope filtering with multiple drop downs using this js code -
jQuery(function() {
var $container = $('#isocontent'),
$select = $('div#filterGroup select');
filters = {};
$container.isotope({
itemSelector: '.box'
});
$select.change(function() {
var $this = $(this);
var $optionSet = $this;
var group = $optionSet.attr('data-filter-group');
filters[group] = $this.find('option:selected').attr('data-filter-value');
var isoFilters = [];
for (var prop in filters) {
isoFilters.push(filters[prop])
}
var selector = isoFilters.join('');
$container.isotope({
filter: selector
});
return false;
});
});
And on the same page I was able to setup a live search input field using the quicksearch.js plugin and isotopes by using this code -
<script type="text/javascript">
$(function () {
$('input#id_search').quicksearch('#isocontent.box');
});
</script>
<script type="text/javascript">
$(function() {
var $container = $('#isocontent');
$container.isotope({
itemSelector: '.box'
});
$('input#filter').quicksearch('#isocontent .box', {
'show': function() {
$(this).addClass('quicksearch-match');
},
'hide': function() {
$(this).removeClass('quicksearch-match');
}
}).keyup(function(){
setTimeout( function() {
$container.isotope({ filter: '.quicksearch-match' }).isotope();
}, 100 );
});
});
</script>
The live search and drop downs work, except they are not working together. When doing a search, it will find the content as should - hiding the irrelevant content- but when filtering with the drop downs, it seems to reset or ignore the filtering done by the live search. Is there away to get the two functions to work together and possibly combine the script into one script??
Any help is appreciated, thanks.
Here is how i have done it.
First, I register a simple text filter.
Then in case a button is clicked, I "extend" this filter by redefining it (by passing a function to isotope's filter option).
If no button is clicked, I re-register the simple text filter.
Isotope initialization:
var qsRegex; // global variable
var $grid = $(container).isotope({
resizable: true,
masonry: {
columnWidth: columnWidth
},
filter: function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
});
Then my keyup event on the input:
var tabPane = $('.tab-pane.active');
var $quicksearch = tabPane.find('.quicksearch').unbind().keyup( debounce( function() {
qsRegex = new RegExp($quicksearch.val(), 'gi');
$grid.isotope();
}, 200 ) );
Then my click event on the buttons:
(simplified)
The addition of $(this).hasClass(filter) is what is important here.
$('.gender-switch').click(function() {
if ($(this).hasClass('active') == false) {
var filter = $(this).attr('data-filter');
var my_combined_filter = function() {
return qsRegex ? ($(this).text().match( qsRegex ) && $(this).hasClass(filter)) : true;
}
$('.tab-pane.active .gallery').isotope({ filter: my_combined_filter });
} else {
// none of the buttons have been selected.
// reset to the simple filter
$('.tab-pane.active .gallery').isotope({ filter: '*' });
var my_simple_filter = function() {
return qsRegex ? $(this).text().match( qsRegex ) : true;
}
$('.tab-pane.active .gallery').isotope({ filter: my_simple_filter });
}
});
Extra: the debounce function in the keyup event
// debounce so filtering doesn't happen every millisecond
function debounce( fn, threshold ) {
var timeout;
return function debounced() {
if ( timeout ) {
clearTimeout( timeout );
}
function delayed() {
fn();
timeout = null;
}
timeout = setTimeout( delayed, threshold || 100 );
}
}

Making a method in a plugin accessible globally?

Given the jQuery dropdown plugin below. Is there a way to add a method that would allow for a separate function outside of the dropdown to 'hideMenu'? Thanks
For example, if I applied the plugin to a div with an ID like so:
$('#settings.dropdown').dropDownMenu();
How could I then call to close the dropDownMenu w hideMenu from outside of the plugin? Thanks
jQuery.fn.dropDownMenu = function() {
// Apply the Dropdown
return this.each(function() {
var dropdown = $(this),
menu = dropdown.next('div.dropdown-menu'),
parent = dropdown.parent();
// For keeping track of what's "open"
var activeClass = 'dropdown-active',
showingDropdown = false,
showingMenu,
showingParent,
opening;
// Dropdown Click to Open
dropdown.click(function(e) {
opening = true; // Track opening so that the body click doesn't close. This allows other js views to bind to the click
e.preventDefault();
if (showingDropdown) {
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
showingDropdown = false;
} else {
showingDropdown = true;
showingMenu = menu;
showingParent = parent;
menu.show();
dropdown.addClass(activeClass);
parent.addClass(activeClass);
}
});
// When you click anywhere on the page, we detect if we need to blur the Dropdown Menu
$('body').click(function(e) {
if (!opening && showingParent) {
var parentElement = showingParent[0];
if (!$.contains(parentElement, e.target) || !parentElement == e.target) {
hideMenu();
}
}
opening = false;
});
// hides the current menu
var hideMenu = function() {
if(showingDropdown) {
showingDropdown = false;
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
}
};
});
};
jQuery advises making multiple methods available through the plugin itself:
jQuery.fn.dropDownMenu = function(method) {
var methods = {
init: function() {
// Put all your init code here
},
hide: function() {
hideMenu();
}
};
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
function hideMenu() {
// ...
}
};
See http://docs.jquery.com/Plugins/Authoring#Plugin_Methods
Update: Use like this:
// Use the plugin normally to run the init method
$('#settings.dropdown').dropDownMenu();
// Call the hide method
$('#settings.dropdown').dropDownMenu('hide');
Sure. Give hideMenu to the global window object, like this:
window["hideMenu"] = function() {
if(showingDropdown) {
showingDropdown = false;
dropdown.removeClass(activeClass);
parent.removeClass(activeClass);
showingMenu.hide();
}
};
You can then call it as usual anywhere you need to.

jquery data returns 'undefined'

I'm trying to store some data in DOM Elements ($.cache) on my plugin but I am facing some problem as mentioned below...
$(_buelement).data('yazi') returns undefined on metodlar.gizle but it works on metodlar.goster where I store the data.
In my plugin metodlar.goster initiates on onMounseIn and metodlar.gizle onMouseOut.
$.fn.balon = function( metod, girdi ) {
var _bu = this;
var metodlar = {
goster : function( ) {
return _bu.each(function ( ) {
var _buelement = $(this);
s.pozisyonAl(_buelement);
s.balon.fadeIn(300);
$.data(_buelement,{'balon' : s.balon,'yazi':'heyho'});
})
},
gizle : function( ) {
return _bu.each(function ( ) {
var _buelement = $(this);
$(_buelement).data('yazi');
})
}
}
});
Finally I ran some debug and found out the metodlar.gizle is just works fine but data is still undefined.
Here's Fiddle Link : http://jsfiddle.net/4FfWz/4/
Try changing the way you store the data in goster:
_buelement.data({'balon' : s.balon,'yazi':'heyho'});
Try this:
gizle : function( ) {
return _bu.each(function ( ) {
var _buelement = $(this);
_buelement.data('yazi');
})
}

Categories