How do I add a function to an element via jQuery? - javascript

I want to do something like this:
$('.dynamicHtmlForm').validate = function() {
return true;
}
$('.dynamicHtmlForm .saveButton').click(function() {
if (!$(this).closest('.dynamicHtmlForm').validate()) {
return false;
}
return true;
});
And then when I have a form of class dynamicHtmlForm, I want to be able to provide a custom validate() function:
$('#myDynamicHtmlForm').validate = function() {
// do some validation
if (there are errors) {
return false;
}
return true;
}
But I get this when I do this:
$(this).closest(".dynamicHtmlForm").validate is not a function
Is what I've described even possible? If so, what am I doing wrong?

Yes, it is technically possible. You will need to reference the element itself, however, and not the jQuery collection. This should work:
$('.dynamicHtmlForm').each(function (ix,o) {
o.validate = function() {
return true;
}
});
$('.dynamicHtmlForm .saveButton').click(function() {
if ($(this).closest('.dynamicHtmlForm')[0].validate()) {
return false;
}
return true;
}

jQuery.fn.validate = function(options) {
var defaults = {
validateOPtions1 : '',
validateOPtions2 : ''
};
var settings = $.extend({}, defaults, options);
return this.each(function() {
// you validation code goes here
});
};
$(document).ready(function() {
$('selector').click(function() {
$('some selector').validate();
// or if you used any options in your code that you
// want the user to enter. then you go :
$('some selector').validate({
validateOPtions1: 'value1',
validateOPtions2: 'value2'
});
});
});

You're not adding the function to the element, you're adding it to the jQuery wrapper around the element. Every time you pass a selector to jQuery, it will create a new wrapper for the found elements:
$('#myEl'); // gives a jQuery wrapper object
$('#myEl'); // creates another jQuery wrapper object
If you save the wrapped element to a variable and use that later, it would be a different story because you're accessing the saved jQuery wrapper object.
var dynamicHtmlForm = $('.dynamicHtmlForm');
dynamicHtmlForm.validate = function() {
return true;
}
$('.dynamicHtmlForm .saveButton').click(function() {
if (dynamicHtmlForm.validate()) {
return false;
}
return true;
}
You could also add the function directly to the element using
$('.dynamicHtmlForm')[0].validate = function () { return true; }
// and later...
if (!$(this).closest('.dynamicHtmlForm')[0].validate())
Or you could look at extending jQuery properly by writing a plugin.

Related

How can I use ng-if to display a button based on the current user?

I am create a user profile and would like to show an edit button that only displays for the profile's owner. I am attempting to use ng-if so that the edit capabilities are removed from the DOM.
How can I use ng-if to display a button based on the current user?
In my view:
<button ng-if="editProfile()">EDIT</button>
In my controller:
$scope.editProfile = function (canedit) {
var canedit = false;
for (var i=0;i<$scope.users.length;i++) {
if ($scope.users[i].id===$scope.currentUser.id) {
canedit = true;
}
}
}
Your function needs to return true or false
<button ng-if="editProfile()">EDIT</button>
$scope.editProfile = function () {
for (var i=0;i<$scope.users.length;i++) {
if ($scope.users[i].id===$scope.currentUser.id) {
return true
}
}
return false
}
You are returning nothing from your function.
You can make it this way
<button ng-if="canEdit()">EDIT</button>
and in your controller
$scope.canEdit = function () {
return $scope.users.find(u=>u.id === $scope.currentUser.id) ? true : false;
}
Just add
return canedit;
below the for closing bracket
As everyone mentioned, the function wasn't returning true or false. After adding the return I discovered that the for statement was extraneous and I just needed a simple check. This code is working correctly:
$scope.editProfile = function () {
if ($scope.user.id===$scope.currentUser.id) {
return true
}
return false
}

jQuery Plugin: Add method with .each

I have the following code in a jQuery plugin:
$.fn.myForm = function() {
return this.each(function() {
var myForm = new MyForm(this);
$.data(myForm, 'myForm');
});
};
I thought that doing this, would allow me to then access the inner functions of myForm, such as getForm
var MyForm = function() {
//...
function getForm() {
return 'Hi';
}
}
But when I try to access myForm from outside the plugin, I get undefined:
$('#test').myForm();
$('#test').data('myForm')
> undefined
What am I doing wrong here?
set your data like this:-
$(this).data('myForm', myForm);

jQuery plugin instances variable with event handlers

I am writing my first jQuery plugin which is a tree browser. It shall first show the top level elements and on click go deeper and show (depending on level) the children in a different way.
I got this up and running already. But now I want to implement a "back" functionality and for this I need to store an array of clicked elements for each instance of the tree browser (if multiple are on the page).
I know that I can put instance private variables with "this." in the plugin.
But if I assign an event handler of the onClick on a topic, how do I get this instance private variable? $(this) is referencing the clicked element at this moment.
Could please anyone give me an advise or a link to a tutorial how to get this done?
I only found tutorial for instance specific variables without event handlers involved.
Any help is appreciated.
Thanks in advance.
UPDATE: I cleaned out the huge code generation and kept the logical structure. This is my code:
(function ($) {
$.fn.myTreeBrowser = function (options) {
clickedElements = [];
var defaults = {
textColor: "#000",
backgroundColor: "#fff",
fontSize: "1em",
titleAttribute: "Title",
idAttribute: "Id",
parentIdAttribute: "ParentId",
levelAttribute: "Level",
treeData: {}
};
var opts = $.extend({}, $.fn.myTreeBrowser.defaults, options);
function getTreeData(id) {
if (opts.data) {
$.ajax(opts.data, { async: false, data: { Id: id } }).success(function (resultdata) {
opts.treeData = resultdata;
});
}
}
function onClick() {
var id = $(this).attr('data-id');
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, id);
}
function handleOnClick(parentContainer, id) {
if (opts.onTopicClicked) {
opts.onTopicClicked(id);
}
clickedElements.push(id);
if (id) {
var clickedElement = $.grep(opts.treeData, function (n, i) { return n[opts.idAttribute] === id })[0];
switch (clickedElement[opts.levelAttribute]) {
case 1:
renderLevel2(parentContainer, clickedElement);
break;
case 3:
renderLevel3(parentContainer, clickedElement);
break;
default:
debug('invalid level element clicked');
}
} else {
renderTopLevel(parentContainer);
}
}
function getParentContainer(elem) {
return $(elem).parents('div.myBrowserContainer').parents()[0];
}
function onBackButtonClick() {
clickedElements.pop(); // remove actual element to get the one before
var lastClickedId = clickedElements.pop();
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, lastClickedId);
}
function renderLevel2(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderLevel3(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderTopLevel(parentContainer) {
parentContainer.html('');
var browsercontainer = $('<div>').addClass('fct-page-pa fct-bs-container-fluid pexPAs myBrowserContainer').appendTo(parentContainer);
// rendering the top level display
}
getTreeData();
//top level rendering! Lower levels are rendered in event handlers.
$(this).each(function () {
renderTopLevel($(this));
});
return this;
};
// Private function for debugging.
function debug(debugText) {
if (window.console && window.console.log) {
window.console.log(debugText);
}
};
}(jQuery));
Just use one more class variable and pass this to it. Usually I call it self. So var self = this; in constructor of your plugin Class and you are good to go.
Object oriented way:
function YourPlugin(){
var self = this;
}
YourPlugin.prototype = {
constructor: YourPlugin,
clickHandler: function(){
// here the self works
}
}
Check this Fiddle
Or simple way of passing data to eventHandler:
$( "#foo" ).bind( "click", {
self: this
}, function( event ) {
alert( event.data.self);
});
You could use the jQuery proxy function:
$(yourElement).bind("click", $.proxy(this.yourFunction, this));
You can then use this in yourFunction as the this in your plugin.

Define a javascript variable under conditions with jquery

Like the title says, I would like to fill a variable up under some conditions
I thought I could do like that but no :
var content = $(function() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
});
So I thought that the javascript variable 'content' would be one of the jquery object representing an element in the dom. But it seems that 'content' is the function.
I guess you imagine what i want to do.. What is the syntax with JQuery ?
Thank you
$(function() { }) is short-code for the DOMReady event. You need to explicitly define a function, and then assign the return value to your variable.
For example:
function getObj()
{
if($('#content').length)
{
return $('#content');
}
if($('#content_no_decoration').length)
{
return $('#contenu_no_decoration');
}
if($('#full_content').length)
{
return $('#full_content');
}
if($('#full_content_no_decoration').length)
{
return $('#full_content_no_decoration');
}
}
You can then assign the value as :
var content = getObj();
You will need to call the assignment when the DOM is ready though, otherwise the selectors will not trigger as expected. For example:
$(function() {
var content = getObj();
});
You are only declaring the function, so content contains a pointer to the function.
Execute it and you are fine:
var content = function() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
}();
But you don't really need a function here. If the script tag is at the bottom of the page (right before the closing </body>-tag), or the assignment is within a load handler you could use:
var content = $('#content').length
? $('#content')
: $('#content_no_decoration').length
? $('#content_no_decoration')
: $('#full_content').length
? $('#full_content')
: $('#full_content_no_decoration').length
? $('#full_content_no_decoration')
: undefined;
Or use jQuery to your advantage and keep things really short:
var content =
$('#content,#content_no_decoration,#full_content,#full_content_no_decoration')
.get(0);
// if none of the elements exist, content will be undefined, otherwise
// it will contain [a JQuery Object of] the first existing element
why you don't do like that ?
function thatsAGoodName() {
if ($('#content').length) {
return $('#content');
}
if ($('#content_no_decoration').length) {
return $('#contenu_no_decoration');
}
if ($('#full_content').length) {
return $('#full_content');
}
if ($('#full_content_no_decoration').length) {
return $('#full_content_no_decoration');
}
}
var content = thatsAGoodName();
The function
$(function() {
// DOM safe to use do stuff
})
Is shorthand for the document ready event. This tells you the coder that the dom is safe to use.
You would not really return anything from this event.
content is an object because you're setting it to a object here:
var content = $(function() {
What you probably intended was:
var content;
if ($('#content').length) {
content = $('#content');
}
if ($('#content_no_decoration').length) {
content = $('#contenu_no_decoration'); // Is #contenu a typo???
}
if ($('#full_content').length) {
content = $('#full_content');
}
if ($('#full_content_no_decoration').length) {
content = $('#full_content_no_decoration');
}
Note, that this will have a reference to an element now. If you want the actual content you'll need to pull it out with something like html() or val().
You are using the shorthand for the jQuery ready event ($(function() {. What I believe you want is a self invoking function:
// remove the call to jQuery
var content = (function() {
if ($('#content').length) {
return $('#content');
}
// ... more
})(); // invoke the function, which should return a jQuery object
You may need to wrap this in a document.ready, depending on where your script is executed.
Rearrange it a little bit and it should work:
$(function () {
var content = (function() {
var regularContent = $('#content');
if (regularContent.length !== 0) {
return regularContent;
}
var contentNoDecoration = $('#content_no_decoration');
if (contentNoDecoration.length !== 0) {
return contentNoDecoration;
}
var fullContent = $('#full_content');
if (fullContent.length !== 0) {
return fullContent;
}
var fullContentNoDecoration = $('#full_content_no_decoration');
if (fullContentNoDecoration.length !== 0) {
return fullContentNoDecoration;
}
}());
});
This code is basically saying once the DOM is ready (the $(function () { ... }); part), run this anonymous function (the (function () { ... }()); part) and assign its return value to content.
Edit: Also, you're losing efficiency by running each of your selectors twice instead of just once.
It's true that content is the function, but you can use that function. Like:
var result = content();
Edit:
Remove the $() around var content = $({/* code */}) and it works.

AngularJS : How to run JavaScript from inside Directive after directive is compiled and linked

I have a responsive template that I am trying to use with my Angularjs app. This is also my first Angular app so I know I have many mistakes and re-factoring in my future.
I have read enough about angular that I know DOM manipulations are suppose to go inside a directive.
I have a javascript object responsible for template re-sizes the side menu and basically the outer shell of the template. I moved all of this code into a directive and named it responsive-theme.
First I added all the methods that are being used and then I defined the App object at the bottom. I removed the function bodies to shorten the code.
Basically the object at the bottom is a helper object to use with all the methods.
var directive = angular.module('bac.directive-manager');
directive.directive('responsiveTheme', function() {
return {
restrict: "A",
link: function($scope, element, attrs) {
// IE mode
var isRTL = false;
var isIE8 = false;
var isIE9 = false;
var isIE10 = false;
var sidebarWidth = 225;
var sidebarCollapsedWidth = 35;
var responsiveHandlers = [];
// theme layout color set
var layoutColorCodes = {
};
// last popep popover
var lastPopedPopover;
var handleInit = function() {
};
var handleDesktopTabletContents = function () {
};
var handleSidebarState = function () {
};
var runResponsiveHandlers = function () {
};
var handleResponsive = function () {
};
var handleResponsiveOnInit = function () {
};
var handleResponsiveOnResize = function () {
};
var handleSidebarAndContentHeight = function () {
};
var handleSidebarMenu = function () {
};
var _calculateFixedSidebarViewportHeight = function () {
};
var handleFixedSidebar = function () {
};
var handleFixedSidebarHoverable = function () {
};
var handleSidebarToggler = function () {
};
var handleHorizontalMenu = function () {
};
var handleGoTop = function () {
};
var handlePortletTools = function () {
};
var handleUniform = function () {
};
var handleAccordions = function () {
};
var handleTabs = function () {
};
var handleScrollers = function () {
};
var handleTooltips = function () {
};
var handleDropdowns = function () {
};
var handleModal = function () {
};
var handlePopovers = function () {
};
var handleChoosenSelect = function () {
};
var handleFancybox = function () {
};
var handleTheme = function () {
};
var handleFixInputPlaceholderForIE = function () {
};
var handleFullScreenMode = function() {
};
$scope.App = {
//main function to initiate template pages
init: function () {
//IMPORTANT!!!: Do not modify the core handlers call order.
//core handlers
handleInit();
handleResponsiveOnResize(); // set and handle responsive
handleUniform();
handleScrollers(); // handles slim scrolling contents
handleResponsiveOnInit(); // handler responsive elements on page load
//layout handlers
handleFixedSidebar(); // handles fixed sidebar menu
handleFixedSidebarHoverable(); // handles fixed sidebar on hover effect
handleSidebarMenu(); // handles main menu
handleHorizontalMenu(); // handles horizontal menu
handleSidebarToggler(); // handles sidebar hide/show
handleFixInputPlaceholderForIE(); // fixes/enables html5 placeholder attribute for IE9, IE8
handleGoTop(); //handles scroll to top functionality in the footer
handleTheme(); // handles style customer tool
//ui component handlers
handlePortletTools(); // handles portlet action bar functionality(refresh, configure, toggle, remove)
handleDropdowns(); // handle dropdowns
handleTabs(); // handle tabs
handleTooltips(); // handle bootstrap tooltips
handlePopovers(); // handles bootstrap popovers
handleAccordions(); //handles accordions
handleChoosenSelect(); // handles bootstrap chosen dropdowns
handleModal();
$scope.App.addResponsiveHandler(handleChoosenSelect); // reinitiate chosen dropdown on main content resize. disable this line if you don't really use chosen dropdowns.
handleFullScreenMode(); // handles full screen
},
fixContentHeight: function () {
handleSidebarAndContentHeight();
},
setLastPopedPopover: function (el) {
lastPopedPopover = el;
},
addResponsiveHandler: function (func) {
responsiveHandlers.push(func);
},
// useful function to make equal height for contacts stand side by side
setEqualHeight: function (els) {
var tallestEl = 0;
els = jQuery(els);
els.each(function () {
var currentHeight = $(this).height();
if (currentHeight > tallestEl) {
tallestColumn = currentHeight;
}
});
els.height(tallestEl);
},
// wrapper function to scroll to an element
scrollTo: function (el, offeset) {
pos = el ? el.offset().top : 0;
jQuery('html,body').animate({
scrollTop: pos + (offeset ? offeset : 0)
}, 'slow');
},
scrollTop: function () {
App.scrollTo();
},
// wrapper function to block element(indicate loading)
blockUI: function (ele, centerY) {
var el = jQuery(ele);
el.block({
message: '<img src="./assets/img/ajax-loading.gif" align="">',
centerY: centerY !== undefined ? centerY : true,
css: {
top: '10%',
border: 'none',
padding: '2px',
backgroundColor: 'none'
},
overlayCSS: {
backgroundColor: '#000',
opacity: 0.05,
cursor: 'wait'
}
});
},
// wrapper function to un-block element(finish loading)
unblockUI: function (el) {
jQuery(el).unblock({
onUnblock: function () {
jQuery(el).removeAttr("style");
}
});
},
// initializes uniform elements
initUniform: function (els) {
if (els) {
jQuery(els).each(function () {
if ($(this).parents(".checker").size() === 0) {
$(this).show();
$(this).uniform();
}
});
} else {
handleUniform();
}
},
updateUniform : function(els) {
$.uniform.update(els);
},
// initializes choosen dropdowns
initChosenSelect: function (els) {
$(els).chosen({
allow_single_deselect: true
});
},
initFancybox: function () {
handleFancybox();
},
getActualVal: function (ele) {
var el = jQuery(ele);
if (el.val() === el.attr("placeholder")) {
return "";
}
return el.val();
},
getURLParameter: function (paramName) {
var searchString = window.location.search.substring(1),
i, val, params = searchString.split("&");
for (i = 0; i < params.length; i++) {
val = params[i].split("=");
if (val[0] == paramName) {
return unescape(val[1]);
}
}
return null;
},
// check for device touch support
isTouchDevice: function () {
try {
document.createEvent("TouchEvent");
return true;
} catch (e) {
return false;
}
},
isIE8: function () {
return isIE8;
},
isRTL: function () {
return isRTL;
},
getLayoutColorCode: function (name) {
if (layoutColorCodes[name]) {
return layoutColorCodes[name];
} else {
return '';
}
}
};
}
};
});
Originally the App.init() object method would be called at the bottom of any regular html page, and I have others that do certain things also that would be used on specific pages like Login.init() for the login page and so forth.
I did read that stackoverflow post
"Thinking in AngularJS" if I have a jQuery background? and realize that I am trying to go backwards in a sense, but I want to use this template that I have so I need to retro fit this solution.
I am trying to use this directive on my body tag.
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse" ng-controller="SidemenuController">
<sidemenu></sidemenu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
So here is my problem. This kinda sorta works. I don't get any console errors but when I try to use my side menu which the javascript for it is in the directive it doesn't work until I go inside the console and type App.init(). After that all of the template javascript works. I want to know how to do responsive theme stuff in these directives. I have tried using it both in the compile and link sections. I have tried putting the code in compile and link and calling the $scope.App.init() from a controller and also at the bottom after defining everything. I also tried putting this in jsfiddle but can't show a true example without having the console to call App.init().
My end design would be having some way to switch the pages through ui-router and when a route gets switched it calls the appropriate methods or re-runs the directive or something. The only method that will run on every page is the App.init() method and everything else is really page specific. And technically since this is a single page app the App.init() only needs to run once for the application. I have it tied to a parent template inside ui-router and the pages that will switch all use this shell template. There are some objects that need to access other to call their methods.
Im sorry in advance for maybe a confusing post. I am struggling right now trying to put together some of the ways that you do things from an angular perspective. I will continue to edit the post as I get responses to give further examples.
You said I have read enough about angular that I know DOM manipulations are suppose to go inside a directive but it sounds like you missed the point of a directive. A directive should handle DOM manipulation, yes, but not one directive for the entire page. Each element (or segment) of the page should have its own directive (assuming DOM manip needs to be done on that element) and then the $controller should handle the interactions between those elements and your data (or model).
You've created one gigantic directive and are trying to have it do way too much. Thankfully, you've kinda sorta designed your code in such a way that it shouldn't be too hard to break it up into several directives. Basically, each of your handle functions should be its own directive.
So you'd have something like:
.directive('sidebarMenu', function(){
return {
template: 'path/to/sidebar/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleSidebarMenu()' function here
}
};
})
.directive('horizontalMenu', function(){
return {
template: 'path/to/horizontal/partial.html',
link: function(scope, elem, attrs){
// insert the code for your 'handleHorizontalMenu()' function here
}
};
})
and then your view would look something like:
<body ui-view="dashboard-shell" responsive-theme>
<div class="page-container">
<div class="page-sidebar nav-collapse collapse">
<horizontal-menu></horizontal-menu>
<sidebar-menu></sidebar-menu>
</div>
<div class="page-content" ui-view="dashboard">
</div>
</div>
</body>
And then you don't need a SidebarmenuController because your controller functions shouldn't be handling DOM elements like the sidebar. The controller should just handling the data that you're going to display in your view, and then the view (or .html file) will handle the displaying and manipulation of that data by its use of the directives you've written.
Does that make sense? Just try breaking that huge directive up into many smaller directives that handle specific elements or specific tasks in the DOM.

Categories

Resources