I have a parent model and child model.
I want to make it mandatory that at least one child is created when a new parent is created.
I am using the nested_form gem and currently using this script to limit the number of children added per record.
$(function() {
var fieldsCount,
maxFieldsCount = 4,
$addLink = $('a.add_nested_fields');
function toggleAddLink() {
$addLink.toggle(fieldsCount <= maxFieldsCount)
}
$(document).on('nested:fieldAdded', function() {
fieldsCount += 1;
toggleAddLink();
});
$(document).on('nested:fieldRemoved', function() {
fieldsCount -= 1;
toggleAddLink();
});
// count existing nested fields after page was loaded
fieldsCount = $('form .fields').length;
toggleAddLink();
})
Is there a way for me to also include a "minimum" requirement? Lets say (1) child? Basically remove the "remove link" if only (1) nested form is visible.
Here is the code I figured out on how to make sure at least one field is presented by removing the "Remove" link based on its count
$(function() {
var fieldsCount,
maxFieldsCount = 2,
$addLink = $('a.add_nested_fields');
var mfieldsCount,
minFieldsCount = 0, // placed at 0 because first object is auto built from controller
$removeLink = $('a.remove_nested_fields');
function toggleAddLink() {
$addLink.toggle(fieldsCount <= maxFieldsCount)
}
function toggleRemoveLink() {
$removeLink.toggle(mfieldsCount <= minFieldsCount)
}
$(document).on('nested:fieldAdded', function() {
fieldsCount += 1;
toggleAddLink();
});
$(document).on('nested:fieldRemoved', function() {
fieldsCount -= 1;
toggleAddLink();
});
$(document).on('nested:fieldRemoved', function() {
mfieldsCount -= 0;
toggleRemoveLink();
});
// count existing nested fields after page was loaded
fieldsCount = $('form .fields').length;
toggleAddLink();
mfieldsCount = $('form .fields').length;
toggleRemoveLink();
})
You can just call a build method inside your parent's controller in action new.
So, when you go parent/new, you will have one child already created. It's just like that:
#parent.children.build
Related
I have two functions that are triggered whilst the user is inputting data. They essentially add up the values of the options they choose, and output them.
On this form, in particular, the options are already pre-populated. Because of this, the functions have not been triggered, leaving their calculation as null.
The functions are shown just above </body>
Functions:
$(calculateScore);
function calculateScore () {
var fields = $('.form-group #input').change(calculate);
function calculate () {
var score = 0;
fields.each(function () {
score += +$(this).val();
});
$('#score').html(score.toFixed(0));
}
}
$(calculateHiddenScore);
function calculateHiddenScore () {
var fields = $('.form-group #input').change(calculate);
function calculate () {
var score = 0;
fields.each(function () {
score += +$(this).val();
});
$('#hidden_score').val(score.toFixed(0));
}
}
Code placed underneath the functions to try and trigger them:
$(function () {
calculateHiddenScore();
calculateScore();
});
and I have also tried:
window.onload = function () {
calculateScore();
calculateHiddenScore();
};
How can I trigger these two functions when the page has loaded please? Many thanks.
DOM ready will not trigger an onchange event even if your items are pre-populated.
Therefore you have to modify a bit your script like:
function calculateScore() {
var fields = $('.form-group #input'); // Cache only!
function calculate() {
var score = 0;
fields.each(function() {
score += +$(this).val();
});
$('#score').html(score.toFixed(0));
$('#hidden_score').val(score.toFixed(0));
}
calculate(); // Calculate ASAP (on DOM ready)
fields.on("change", calculate); // and also on change
}
jQuery(function($) { // DOM is ready and $ alias secured
calculateScore(); // Trigger
// other jQuery code here
});
P.S: BTW even if the above is a bit improved, it makes not much sense to loop using each over a single ID #input element - I'll leave that to you...
So I have the following code I have written to build a carousel in JavaScript using Hammer.js and jQuery:
var hCarousel = {
container: false,
panes: false,
pane_width: 0,
pane_count: 0,
current_pane: 0,
build: function( element ) {
hCarousel.container = $(element).find('.hcarousel-inner-container');
hCarousel.panes = $(hCarousel.container).find('> .section');
hCarousel.pane_width = 0;
hCarousel.pane_count = hCarousel.panes.length;
hCarousel.current_pane = 0;
hCarousel.setPaneDimensions( element );
$(window).on('load resize orientationchange', function() {
hCarousel.setPaneDimensions( element );
});
$(element).hammer({ drag_lock_to_axis: true })
.on('release dragleft dragright swipeleft swiperight', hCarousel.handleHammer);
},
setPaneDimensions: function( element ){
hCarousel.pane_width = $(element).width();
hCarousel.panes.each(function() {
$(this).width(hCarousel.pane_width);
});
hCarousel.container.width(hCarousel.pane_width*hCarousel.pane_count);
},
next: function() {
return hCarousel.showPane(hCarousel.current_pane+1, true);
},
prev: function() {
return hCarousel.showPane(hCarousel.current_pane-1, true);
},
showPane: function( index ) {
// between the bounds
index = Math.max(0, Math.min(index, hCarousel.pane_count-1));
hCarousel.current_pane = index;
var offset = -((100/hCarousel.pane_count)*hCarousel.current_pane);
hCarousel.setContainerOffset(offset, true);
},
setContainerOffset: function( percent, animate ) {
hCarousel.container.removeClass("animate");
if(animate) {
hCarousel.container.addClass("animate");
}
if(Modernizr.csstransforms3d) {
hCarousel.container.css("transform", "translate3d("+ percent +"%,0,0) scale3d(1,1,1)");
}
else if(Modernizr.csstransforms) {
hCarousel.container.css("transform", "translate("+ percent +"%,0)");
}
else {
var px = ((hCarousel.pane_width*hCarousel.pane_count) / 100) * percent;
hCarousel.container.css("left", px+"px");
}
},
handleHammer: function( ev ) {
ev.gesture.preventDefault();
switch(ev.type) {
case 'dragright':
case 'dragleft':
// stick to the finger
var pane_offset = -(100/hCarousel.pane_count)*hCarousel.current_pane;
var drag_offset = ((100/hCarousel.pane_width)*ev.gesture.deltaX) / hCarousel.pane_count;
// slow down at the first and last pane
if((hCarousel.current_pane == 0 && ev.gesture.direction == Hammer.DIRECTION_RIGHT) ||
(hCarousel.current_pane == hCarousel.pane_count-1 && ev.gesture.direction == Hammer.DIRECTION_LEFT)) {
drag_offset *= .4;
}
hCarousel.setContainerOffset(drag_offset + pane_offset);
break;
case 'swipeleft':
hCarousel.next();
ev.gesture.stopDetect();
break;
case 'swiperight':
hCarousel.prev();
ev.gesture.stopDetect();
break;
case 'release':
// more then 50% moved, navigate
if(Math.abs(ev.gesture.deltaX) > hCarousel.pane_width/2) {
if(ev.gesture.direction == 'right') {
hCarousel.prev();
} else {
hCarousel.next();
}
}
else {
hCarousel.showPane(hCarousel.current_pane, true);
}
break;
}
}
}
And I call this like:
var hSections;
$(document).ready(function(){
hSections = hCarousel.build('.hcarousel-container');
});
Which works fine. But I want to make it so that I can have multiple carousels on the page which again works... but the overall width of the container is incorrect because it's combining the width of both carousels.
How can I run multiple instances of something like this, but the code know WHICH instance it's interacting with so things don't become mixed up, etc.
The problem is your design is not really suited to multiple instances, because of the object literal which has properties of the carousel, but also the build method.
If I was starting this from scratch, I would prefer a more OOP design, with a carousel class that can you instantiate, or have it as a jQuery plugin. That said, it's not impossible to adapt your existing code.
function hCarousel(selector){
function hCarouselInstance(element){
var hCarousel = {
// insert whole hCarousel object code
container: false,
panes: false,
build : function( element ){
...
};
this.hCarousel = hCarousel;
hCarousel.build(element);
}
var instances = [];
$(selector).each(function(){
instances.push(new hCarouselInstance(this));
});
return instances;
}
Usage
For example, all elements with the hcarousel-container class will become an independant carousel.
$(document).ready(function(){
var instances = hCarousel('.hcarousel-container');
});
Explanation:
The hCarousel function is called passing the selector, which can match multiple elements. It could also be called multiple times if needed.
The inner hCarouselInstance is to be used like a class, and instantiated using the new keyword. When hCarousel is called, it iterates over the matched elements and creates a new instance of hCarouselInstance.
Now, hCarouselInstance is a self contained function that houses your original hCarousel object, and after creating the object it calls hCarousel.build().
The instances return value is an array containing each instance object. You can access the hCarousel properties and methods from there, such as:
instances[0].hCarousel.panes;
jQuery plugin
Below is a conversion to a jQuery plugin, which will work for multiple carousels.
(function ( $ ) {
$.fn.hCarousel = function( ) {
return this.each(function( ) {
var hCarousel = {
// insert whole hCarousel object code here - same as in the question
};
hCarousel.build(this);
});
};
}( jQuery ));
Plugin usage:
$('.hcarousel-container').hCarousel();
I would try turning it into a function which you can use like a class. Then you can create separate objects for your carousels.
So you would have something like the following:
function HCarousel (element) {
this.element=element;
this.container= false;
this.panes= false;
this.pane_width= 0;
this.pane_count= 0;
this.current_pane= 0;
}
And then add each method on the class like this.
HCarousel.prototype.build = function() {
this.container = $(element).find('.hcarousel-inner-container');
this.panes = $(hCarousel.container).find('> .section');
this.pane_width = 0;
this.pane_count = hCarousel.panes.length;
this.current_pane = 0;
this.setPaneDimensions( element );
$(window).on('load resize orientationchange', function() {
this.setPaneDimensions( element );
});
$(this.element).hammer({ drag_lock_to_axis: true }).on('release dragleft dragright swipeleft swiperight', hCarousel.handleHammer);
};
etc. That should give you the basic idea. Will take a little bit of re-writing, but then you can create a carousel with something like this:
var carousel1 = new HCarousel('.hcarousel-container');
Hope that puts you on the right track.
Classes don't actually exist in JS, but this is a way to simulate one using a function. Here's a good article on using classes in JS http://www.phpied.com/3-ways-to-define-a-javascript-class/
I have a function that writes out to a cooke the value of the DIV that holds that data that I want to show, the cookie code works, the toggle code works but when the page refreshses, I can get the list of repeater elements, itterate through them, determine if the section should be hidden or not but I can't use visible, I can't use .show() or .hide(), I know this has to be easy but what am I over looking???
This is my working code for the slidetoggle that works and writes the true or false to the cooke based on the repeater title attribute:
$(document).ready(function () {
$("a.toggle").click(function () {
var inObj = $(this).parent().find('div#fader');
var inTitle = inObj.attr('title');
inObj.slideToggle('fast', function () {
docCookies.setItem(inTitle, inObj.is(':visible').toString());
});
});
});
This is the code block that I have the problem with, specifically, the .show() and the .hide() are not known methods, so I have the object in inObj[] collection, I am not sure how to cast this or deal with this in javascript.....
$(window).load(function () {
var inObj = $('div#fader');
for (var i = 0; i < inObj.length; i++) {
var objTitle = inObj[i].title;
var item = docCookies.getItem(objTitle);
if (item == "true") {
inObj[i].show();
}
else {
inObj[i].hide();
}
}
});
Use $(inObj[i]).show() and $(inObj[i]).hide().
If you take a look at this fiddle it will seem fine, but if you click next and move down 2 to 3 times, and then click "memory" (in top nav) it takes .active back to the first .item,
then if you click 'NEXT' again it continues to go to the next element from the prior one we left off of.
I am trying to reset it and continue based on where we go after clicking on the top nav.
Faulty jQuery:* Two click functions both adding active*
var items = $('.item'),
currentItem = items.filter('.active'),
last = items.last();
$("#next-button").on('click', function () {
currentItem.removeClass('active');
var nextItem = currentItem.next();
if (nextItem.length) {
currentItem = nextItem.addClass('active');
if (currentItem.is(last)) {
$('#slide-buttons').addClass('red');
}
}
var items = $('.item');
$(".breadcrumb-cell .breadcrumb").click(function () {
var theID = $(this).data("id");
items.filter(function() {
return $(this).data('category') === theID;
}).addClass('active');
});
});
Fiddle
I Googled "how to reset .next() jquery" but couldn't find anything, not sure if that's even the right thing to do?
The problem you had was that currentItem didn't get updated when you clicked on a breadcrumb.
I made a lot of changes, mostly "streamlining" things. I removed your global variables and based the current item on the active class instead. Check: http://jsfiddle.net/kQabJ/17/
$("#next-button").on('click', function () {
var nextItem = $('.active').removeClass('active').next();
if (!nextItem.length) {
nextItem = $('.item').first();
}
nextItem.addClass('active');
});
$(".breadcrumb-cell .breadcrumb").on('click', function () {
$('.active').removeClass('active');
var theID = $(this).data("id");
$("#" + theID).addClass('active');
});
Note that I also modified your DOM a bit to make it easier to select an item when a user clicks a breadcrumb. That change is using an ID on your .items instead of data. This way you can do $("#" + theID) rather than filtering based on data.
Since these things are uniquely identifying your .item elements themselves - it makes since to use an id anyway, but if this is not what you not you can always change that part back.
You just need to update currentItem, see http://jsfiddle.net/kQabJ/13/
$(".breadcrumb-cell .breadcrumb").on('click', function () {
items.removeClass('active');
var theID = $(this).data("id");
items.filter(function() {
return $(this).data('category') === theID;
}).addClass('active');
currentItem = items.filter('.active');
});
Try this code
You were not updating the currentItem, which was causing the problem.
var items = $('.item'),
currentItem = items.filter('.active'),
last = items.last();
$("#next-button").on('click', function () {
currentItem = items.filter('.active');
var nextItem = currentItem.next();
currentItem.next().length > 0 ? currentItem.next().addClass('active')
: items.first().addClass('active');
currentItem.removeClass('active');
});
$(".breadcrumb-cell .breadcrumb").on('click', function () {
items.removeClass('active');
var theID = $(this).data("id");
items.filter(function () {
return $(this).data('category') === theID;
}).addClass('active');
});
Check Fiddle
i get this error, and i don't know how can be solved. I read this link before.
EDIT:1
index.php
<script type="text/javascript">
$(document).ready(function() {
$("#customForm").submit(function() {
var formdata = $("#customForm").serializeArray();
$.ajax({
url: "sent.php",
type: "post",
dataType: "json",
data: formdata,
success: function(data) {
switch (data.livre) {
case 'tags':
$("#msgbox2").fadeTo(200, 0.1, function() {
$(this).html('Empty tags').fadeTo(900, 1);
});
break;
default:
$("#msgbox2").fadeTo(200, 0.1, function() {
$(this).html('Update').fadeTo(900, 1, function() {
$('#conteudo').load('dojo/test_Slider.php');
});
});
break;
}
}
});
return false;
});
});
</script>
test_slider.php
<script type="text/javascript">
var slider = [];
for (i = 0; i < 5; i++) {
slider[i] = (
function(i) {
return function() {
var node = dojo.byId("input"+[i]);
var n = dojo.byId("valores"+[i]);
var rulesNode = document.createElement('div'+[i]);
node.appendChild(rulesNode);
var sliderRules = new dijit.form.HorizontalRule({
count:11,
style:{height:"4px"}
},rulesNode);
var labels = new dijit.form.HorizontalRuleLabels({
style:{height:"1em",fontSize:"75%"},
},n);
var theSlider = new dijit.form.HorizontalSlider({
value:5,
onChange: function(){
console.log(arguments);
},
name:"input"+[i],
onChange:function(val){ dojo.byId('value'+[i]).value = dojo.number.format(1/val,{places:4})},
style:{height:"165px"},
minimum:1,
maximum:9,
}
},node);
theSlider.startup();
sliderRules.startup();
}
})(i);
dojo.addOnLoad(slider[i]);
}
</script>
Problem: First click in submit btn all works well, 5 sliders are imported. Second click, an update is supposed, but i get this message:
Tried to register widget with id==valores0 but that id is already registered
[Demo video]2
Just to add on to #missingo's answer and #Kevin's comment. You could walk through the existing dijits by looking in the registry:
var i = i || 0; // Cache this at the end of your loop
dijit.registry.map(function (widget) {
if (+widget.id.replace(/^[^\d]+/, '') < i) {
widget.destroyRecursive();
}
});
/*
Your loop fixed as described in missingno's answer.
*/
You fell in the age-old trap of making function closures inside a for loop. By the time addOnLoad fires and the sliders are created, i will be equal to 2 and both sliders will try to use the same DOM nodes (something that is not allowed).
You need to make sure that you give a fresh copy of i for everyone. The following is a quick fix:
for(i=0; i<2; i++){
(function(i){
slider[i] = ...
//everything inside here remains the same
//except that they now use their own i from the wrapper function
//instead of sharing the i from outside.
}(i));
}
Dijit stores all active widgets in the dijit.registry, and uses id's as unique qualifiers. You can't create dijits with same id.
Need to clean dojo.registry before create a new slider dijits. Add this code before declare dijit on test_slider.php
dijit.registry["input"+ [i]].destroyRecursive();
can you assign any number ID like ID generated by 10 digit random number or something with datetime combination so id will never be same.