Halaman

Tampilkan postingan dengan label jquery. Tampilkan semua postingan
Tampilkan postingan dengan label jquery. Tampilkan semua postingan

Senin, 04 Juli 2011

fixing option tag display none bug in webkit

The Problem

option tag using style display:none isn't hidden in browser using webkit engine (chrome, safari)
https://bugs.webkit.org/show_bug.cgi?id=8351

The workaround

using jQuery js framework with one simple function taken from jquery.extended_helper_2.6:
(function($) {
  $.fn.extend({
    /* hide select's specified option by removing the option and storing it in variable
     * this function is a workaround to fix webkit bug when hiding select's option:
     * https://bugs.webkit.org/show_bug.cgi?id=8351
     * hideSelectOptions(selector, options)
     * selector:
     * - string selector of options to hide
     * - null or undefined will not hide any options
     * - empty string will hide all options
     * - function will be ran for all options, return true to hide the options
     * options:
     * - toggleHide: true/false, activate/deactive toggle hide/show select if all options is hidden
     * - placeholder: string, add placeholder option if all options is hidden
     * usage:
     *  - $('select.certain_class').hideSelectOptions('.another_class'); // -> show previously hidden options and hide all options with 'another_class' class
     *  - $('select.certain_class').hideSelectOptions(''); // -> hide all options
     *  - $('select.certain_class').hideSelectOptions(); // -> show previously hidden options without hiding any options
     */
    hideSelectOptions: function(selector, options){
      if(!options) options = {};
      this.filter('select').each(function(){
        var $this = $(this);
        // restore previously hidden options and remove any placeholder option
        $this.append($this.data('hiddenOptions')).find('.placeholder_option').remove();

        // return if no selector specified
        if(!selector) return;
        
        // find, detach and store specified hidden options
        var hidden_options = $this.find('option');
        if($.isFunction(selector))
          hidden_options = hidden_options.deleteIf(function(){return !selector.call(this);});
        else
          hidden_options = hidden_options.filter(selector);
        $this.data('hiddenOptions',
          hidden_options
            .removeAttr('selected')
            .detach());

        // find visible options
        var visible_options = $this.find('option');

        // toggle hide/show select depend on visible option found
        if(options.toggleHide)
          $this.toggle(visible_options.length);
        if(options.placeholder && !visible_options.length)
          $this.append('');

        // select first option if no other visible option selected and trigger select change
        if(!visible_options.filter(':selected').length){
          visible_options.filter(':first').attr('selected', 'selected');
          $this.trigger('change');
        }
      });
      return this;
    }
  });
})(jQuery);

The Demonstration

Selasa, 03 Mei 2011

Aborting AJAX Request

For some functions like autocomplete, usually we send ajax request whenever user stop typing (by using timeout), but if the request takes a long time to finish, user might sending another request while the previous one isn't done yet, in worst case scenario this might cause the requests complete order messed up therefore the result will be invalid.

We can prevent this by aborting previous ajax request before sending a new one.
XMLHttpRequest object have abort() function we can use for that purpose, documented here:
https://developer.mozilla.org/en/XMLHttpRequest#abort

in the following jquery script, i have a 2 text fields sending ajax request onkeyup that always completed in 2 seconds and adding the inputted text in the result div, the first one will have stackable ajax request, and the other one will prevent ajax request stacking by using abort

Kamis, 17 Februari 2011

[shared] javascript deleteIf method

Background story

Di suatu project saia membutuhkan selector jquery spesial yg dapat memfilter data dari suatu element, misalkan saia ingin mengambil semua element dengan class "certain_class" dan mempunyai tooltip (menggunakan jquery.qtip) yg dapat diketahui dari adanya elem.data('qtip').

Tetapi selector data tidak dapat dilakukan (paling tidak saia tidak tahu bagaimana melakukannya), karena itu saia memutuskan untuk membuat method deleteIf untuk array dengan meniru konsep delete_if ruby.

Menggunakan method deleteIf untuk membuang element2 yg tidak mempunyai tooltip, contoh kasus diatas dapat dilakukan dengan:
$('.certain_class').deleteIf(function(){return !$(this).data('qtip');});

Code

$.fn.extend({
  deleteIf: function(fn){
    return $(this.get().deleteIf(fn));
  }
});

$.extend(Array.prototype, {
  deleteIf: function(fn){
    var newArr = [];
    for(var i=0; i < this.length; i++){
      if(!fn.call(this[i], i)) newArr.push(this[i]);
    };
    return newArr;
  }
});

Penjelasan

Method deleteIf yg pertama hanya merupakan shortcut untuk melakukan deleteIf pada jQuery object, intinya hanya merubah jQuery object menjadi Array object dengan method get() kemudian memanggil deleteIf milik Array dan merubah hasilnya kembali menjadi jQuery object.

Method deleteIf di array cukup simple, hanya meng-iterasi array dan memanggil fungsi yg di-passing dengan object yg diiterasi dan mem-push object tersebut ke array baru bila fungsi mengembalikan nilai false.

Kedua method deleteIf ini merupakan bagian dari jquery.extended_helper versi 2.5 (saat ini masi dalam pengerjaan, dan mungkin berubah sewaktu2)

Demo

Selasa, 01 Februari 2011

[shared] identify object type in javascript

UPDATE: i changed the function name to $.typeOf(obj) to prevent ambiguity with css className, please use extendedhelper 2.4b or later

untuk sebagian besar object kita dapat menggunakan typeof, contoh:
typeof object; // will return object class name in string

tetapi untuk bbrp object seperti Array, Date, window, document dll, typeof akan mengembalikan "object" bukan class yg seharusnya, untuk mengakalinya kita dapat menggunakan Object.constructor, contoh fungsi lengkapnya:

$.getClassName = function(obj){
  try{
    var constructorStr = obj.constructor.toString();
    var tempMatch = constructorStr.match(/function (.+)\(/);
    if(!tempMatch || tempMatch.length != 2) tempMatch = constructorStr.match(/ (.+)\]/);
    return tempMatch[1];
  }catch(e){
    return (typeof obj).capitalize();
  }
}

$.getClassName(1); // -> 'Number'
$.getClassName('Aya Hirano'); // -> 'String'
$.getClassName(['A','H']); // -> 'Array'
$.getClassName(null); // -> 'Object'
$.getClassName(undefined); // -> 'Undefined'
$.getClassName(new Date); // -> 'Date'
$.getClassName({'aya':'forever'}); // -> 'Object'
$.getClassName(document); // -> 'HTMLDocument'
$.getClassName(window); // -> 'Window'
$.getClassName(document.getElementsByTagName); // -> 'Function'
$.getClassName(document.getElementsByTagName('div')[0]); // -> 'HTMLDivElement'

fungsi getClassName tersebut ada di jquery.extended_helper 2.4 dan menggunakan fungsi capitalize dari js tersebut.

Demo

Rabu, 22 Desember 2010

[shared] rails render_invalid method

purpose


provide DRY solution for rendering (or redirecting) from controller on invalid request, such as failed validation on create/update

requirements


- jquery js framework
- notifications helper from my earlier post about custom ajax helper:
http://tech.maysora.com/2010/11/rails-custom-ajax-helper-for-jquery.html

codes


application_controller.rb
# render invalid request to show error message depend on request type
# example:
#   render_invalid "Update failed."
#     ajax: show error notification with message "Update failed."
#     html: simply render text "Update failed."
#   render_invalid "Save failed.", :render => {:action => "new"}
#     ajax: show error notification with message "Save failed."
#     html: render action new with "Save failed." flash message
#   render_invalid "Save failed.", :render => {:action => "new"}, :flash => false
#     same as above but without flash for html
#   render_invalid "You can't do that.", :redirect_to => {:action => "index"}, :flash => false, :status => :unauthorized
#     same as above but redirecting to index instead of rendering new for html
#     and using unauthorized(401) status instead of not_acceptable(406)
#   render_invalid "Update failed.", :object => @user, :fields_wrapping => true
#     ajax: show error notification with addition of object's error messages and wrap error fields with div class fieldWithErrors
#     html: same as ajax minus fields wrapping
def render_invalid message="Error.", options={:flash => true}
  options[:status] ||= :not_acceptable
  message += "
#{options[:object].errors.full_messages.join(' ')}
" if options[:object].present? respond_to do |format| format.js do render(:update, :status => options[:status]) do |page| page << notification_error(message) if options[:object].present? && options[:fields_wrapping] page.rjs_clean_error_fields request page.rjs_apply_error_fields options[:object], request end end end format.html do if options[:render].present? flash.now[:error] = message if options[:flash] render options[:render].try(:merge, {:status => options[:status]}) || {:text => message, :status => options[:status]} elsif options[:redirect_to].present? flash[:error] = message if options[:flash] redirect_to options[:redirect_to] else render :text => message, :status => options[:status] end end end end

application_helper.rb (only if you use ajax error fields wrapping)
def rjs_apply_error_fields object, request
  klass_name = object.class.name.underscore
  wrapper = "
" object.errors.each do |attr, msg| attr = attr.split(".") page << "var $fields = $(), $container = $('\##{klass_name}_#{object.id || "new"}');" page << "if(!$container.length) $container = $('form[action=\\'#{request.request_uri}\\']');" page << "if(!$container.length) $container = $(document);" if attr.length > 1 object.send(attr.first).each_with_index do |x,i| page << "$fields = $fields.add('[id^=#{klass_name}_#{attr.first}_attributes][id$=#{attr.last}]:eq(#{i}),label[for^=#{klass_name}_#{attr.first}_attributes][for$=#{attr.last}]:eq(#{i})', $container)" if x.errors.on(attr.last) end else page << "$fields = $('label[for=#{klass_name}_#{attr}],\##{klass_name}_#{attr}', $container);" end page << "if(!$fields.parent('.fieldWithErrors').length)" page << "$fields.wrap('#{wrapper}')" end end def rjs_clean_error_fields request page << "$('.fieldWithErrors.rjs_#{request.request_method}_#{request.request_uri.gsub(/_=\d+($|&)/, "").parameterize} *').unwrap();" end

usage

users_controller.rb (using non ajax form)
#...

def create
  @user = User.new(params[:user])
  unless @user.save
    render_invalid "Failed to register.", :object => @user, :render => {:action => "new"}
  else
    flash[:info] = "Registration success, please check your email to activate."
    redirect_to login_path
  end
end

#...
users_controller.rb (using ajax form)
#...

def update
  @user = current_user
  unless @user.update_attributes(params[:user])
    render_invalid "Profile update failed.", :object => @user, :fields_wrapping => true
  end
end

#...
and update.js.rjs (for success behavior)
page << notification_info "Profile updated."
page.rjs_clean_error_fields request # used for cleaning error fields wrapping
#... other necessary action ...

additional tips

depending on your site style, it might be a good idea to change the div wrapper in rjs_apply_error_fields helper to span. you can also change rails standard error field wrapper for non-ajax form, see this post

Minggu, 21 November 2010

[shared] rails custom ajax helper for jquery

requirements:
- jrails plugin (https://github.com/aaronchi/jrails)
- working jquery.gritter or other js notification (http://boedesign.com/blog/2009/07/11/growl-for-jquery-gritter/)
- my jquery.extended_helper (http://code.google.com/p/jquery-extendedhelper)

installation:
- add these code in application_helper.rb
# Adding additional options for ajax function
# You can use thisElement to manipulate element that using the function (link/button/form)
def custom_remote_function_options options
  options[:before] ||= ""
  options[:loading] ||= ""
  options[:complete] ||= ""
  options[:success] ||= ""
  options[:before] += "; var thisElement=this"
  options[:failure] ||= "; eval(request.responseText)"

  unless options[:loading_text].blank?
    options[:loading] += "; thisElement.notif = #{notification_loading(options[:loading_text])}"
    options[:complete] += "; #{notification_remove "thisElement.notif"}"
  end
  options
end

# Override rails remote_function using additional options
def custom_remote_function options
  remote_function(custom_remote_function_options(options)).html_safe
end

# Override rails link_to_remote function using additional options
# Disabling the link when ajax loading in progress to avoid double request
def custom_link_to_remote name, options={}, html_options=nil
  options = custom_remote_function_options options
  options[:loading] += "; $(thisElement).setLink(false)"
  options[:complete] += "; $(thisElement).setLink(true)"
  link_to_remote name, options, html_options
end

# Override rails button_to_remote function using additional options
# Disabling the button when ajax loading in progress to avoid double request
def custom_button_to_remote name, options={}, html_options=nil
  options = custom_remote_function_options options
  options[:loading] += "; $(thisElement).setInput(false)"
  options[:complete] += "; $(thisElement).setInput(true)"
  button_to_remote name, options, html_options
end

# Adding additional options for ajax form
# Add :unchange_default => true to prevent successful request changing default form fields value
# Add :disable_link => true to disable all link inside form when loading in progress
def custom_remote_form_options options
  options = custom_remote_function_options options
  options[:loading] += "; $(':input', thisElement).setInput(false);"
  options[:complete] += "; $(':input', thisElement).setInput(true);"
  unless options[:unchange_default]
    options[:success] +=  "; $(':input', thisElement).setDefault();"
  end
  if options[:disable_link]
    options[:loading] += "; $('a', thisElement).setLink(false);"
    options[:complete] += "; $('a', thisElement).setLink(true);"
  end
  options
end

# Override rails remote_form_for using additional options
def custom_remote_form_for record_or_name_or_array, *args, &proc
  args.push custom_remote_form_options(args.extract_options!)
  remote_form_for record_or_name_or_array, *args, &proc
end
alias_method :custom_form_remote_for, :custom_remote_form_for

# Override rails form_remote_tag using additional options
def custom_form_remote_tag options={}, &block
  form_remote_tag custom_remote_form_options(options), &block
end
alias_method :custom_remote_form_tag, :custom_form_remote_tag

def custom_button_to_remote name, value, options = {}
  button_to_remote name, value, custom_remote_form_options(options)
end

# Override rails submit_to_remote using additional options
def custom_submit_to_remote(name, value, options = {})
  submit_to_remote(name, value, custom_remote_form_options(options))
end

def notification_info text, no_escape=false
  text.gsub!(/\n/, '
')
  text = escape_javascript(text) unless no_escape
  "notificationInfo('#{text}')"
end

def notification_loading text, no_escape=false
  text.gsub!(/\n/, '
')
  text = escape_javascript(text) unless no_escape
  "notificationLoading('#{text}')"
end

def notification_error text, no_escape=false
  text.gsub!(/\n/, '
')
  text = escape_javascript(text) unless no_escape
  "notificationError('#{text}')"
end

def notification_remove notification
  "notificationRemove(#{notification})"
end

- add notification code in application.js (sample using jquery.gritter)
function notificationInfo(text){
  return $.gritter.add({text: text.replace(/\n/g, '
')});
}

function notificationError(text){
  return $.gritter.add({text: '' + text.replace(/\n/g, '
') + '',time: 6000});
}

function notificationLoading(text){
  return $.gritter.add({text: text.replace(/\n/g, '
'), image: '/images/gritter-loading.gif', sticky:true})
}

function notificationRemove(notification){
  $.gritter.remove(notification);
}

Selasa, 19 Oktober 2010

jQuery ExtendedHelper

http://code.google.com/p/jquery-extendedhelper/

jQuery hash merge

merge hash2 to hash1:

$.extend(hash1, hash2);


for recursive merge:

$.extend(true, hash1, hash2);

Kamis, 30 September 2010

[shared] datejs, impressive js date helpers

http://www.datejs.com/
http://code.google.com/p/datejs/wiki/APIDocumentation

Rabu, 04 Agustus 2010

[shared] Simple jQuery function for form debugging

dalam developing site, seringkali saia membuat form dengan elemen2 seperti select tag, radio button, input hidden dll. normalnya user hanya dapat memasukkan value2 yang sudah saia tentukan pada elemen2 tersebut, tp user yg "iseng" bisa saja memasukkan value di luar value2 yg sudah ditentukan td sehingga dapat merusak aplikasi.

buat mencegah saia harus membuat server side validation yang seringkali terlupakan karena pada saat saia melakukan testing saia melakukannya sebagai user normal bukan sebagai user "iseng". hal tersebut membuat saia berpikir untuk membuat script sederhana untuk mempermudah testing.

menggunakan jQuery ternyata script helper nya cukup simple:

function debugInput(){
$('.debug_input').remove();
$(':input:not(textarea,:text:not(.date),:button,:submit,:reset)').each(function(){
$(this).after('
'+this.name+':
').css('border', 'medium dotted gray');
});
$('form').each(function(){
$(this).append('
'+this.id+':
')
});
$('.debug_input').css('opacity', 0.7).draggable();
}


pada saat dijalankan fungsi tersebut akan menambahkan input text normal di tiap elemen2 form sehingga saia dapat dengan mudah memasukkan value2 aneh di select tag dan sejenisnya. selain itu fungsi ini jg menambahkan tombol submit di form.

contoh pemakaian pada site php manual:

Kamis, 04 Maret 2010

[shared] jQuery serialize only a part of form

baru2 ini di project yang sedang saia kerjakan, saia menemui suatu masalah, intinya saia ingin men-submit suatu bagian dari form yang cukup besar menggunakan ajax untuk mendapatkan nilai yang akan dipakai oleh bagian lain dari form.

secara sederhana kode nya sebagai berikut:

<form>
<input type="text" name="user_name" />
<div id="part1">
<input type="text" name="user_paid_amount" onchange="ajaxRequestToGetPart2ValueBasedOnPart1();" />
<input type="text" name="user_type" onchange="ajaxRequestToGetPart2ValueBasedOnPart1();"/>
</div>
<div id="part2">
<input type="text" name="price_amount" value="" />
</div>
</form>

intinya ketika ada perubahan pada input2 di part1, part2 akan secara otomatis diisi.

namun yang jadi masalah adalah bagaimana cara mengambil value2 dari part1 untuk dikirimkan sebagai parameter..
jquery menyediakan fungsi serialize untuk mengambil semua element dari suatu form (http://api.jquery.com/serialize/), sayangnya hal tersebut tidak dapat dilakukan untuk container selain form, jadi $('#part1').serialize() tidak dapat digunakan..

Solusi 1 - clone(true)

jQuery Serialize a Fieldset
menggunakan clone untuk meng-copy isi div yang kemudian diletakkan pada form yang disembunyikan. form tersebut lalu di-serialize kemudian di-remove.

namun clone merupakan fungsi yang cukup memakan resource.

Solusi 2 - selector

solusi yang ke-2 dengan menggunakan selector :input. kira2 kodenya sebagai berikut:

function serialize(container){
//container: string selector or element of the container
$(':input', container).serialize();
}
serialize('#part1');

lebih simple dan efektif :)