How to handle an option change in a custom JQuery widget? - javascript

I created a widget using JQuery factory. It has an option. The option can be changed after creation of the widget.
How can I handle option change? Is there any events or methods for this?
I want something like this:
$.widget("myWidget", {
options: {
myOption: 'defaultValue'
},
_create: function() {
this.initWidget();
}
initWidget: function() {
//do initialization
}
_onOptionChange: function() {
this.initWidget();
}
}

The solution I found is to override methods _setOption and _setOptions.
_setOptions: function( options ) {
var key;
for ( key in options ) {
this._setOption( key, options[ key ] );
}
return this;
},
_setOption: function( key, value ) {
if (key === "myOption") {
this.initWidget();
}
}

Related

WordPress Inject element into wp.element.createElement

I am working on a plugin that will allow us to add block options to the ACF block system and save the fields in meta. The issue is I am really rusty with js and I am unable to figure out how to merge my loop into the wp.element.create system. I have included my javascript below with comments of what I am trying to accomplish. Any help on this is greatly appreciated.
jQuery(function(){
var el = wp.element.createElement,
Fragment = wp.element.Fragment
registerBlockType = wp.blocks.registerBlockType,
RichText = wp.editor.RichText,
BlockControls = wp.editor.BlockControls,
AlignmentToolbar = wp.editor.AlignmentToolbar,
InspectorControls = wp.editor.InspectorControls;
var withInspectorControls = wp.compose.createHigherOrderComponent( function( BlockEdit ) {
return function( props ) {
// Set data if it does not exist - needed when new page is created
if(props.attributes.data === undefined){ props.attributes.data={}; }
// begin creating the return element
var elm = el(
Fragment,
{},
el( BlockEdit, props ),
el( InspectorControls, {},
el( PanelBody, { title: 'Accordion Options' },
// THIS IS WHERE THE ADDED ELEMENTS WILL GO
)
);
$.each(fields, function( key, field ) {
switch(field.type){
case 'text':
var optionField = el( TextControl, {
label: field.label,
help: field.instructions,
onChange: function(value){
textField=value;
props.setAttributes( { textField: value } );
}
});
// ADD optionField TO elm ABOVE CORRECTLY
break;
default: continue;
}
});
return elm;
};
}, 'withInspectorControls' );
wp.hooks.addFilter( 'editor.BlockEdit', 'acf/accordion', withInspectorControls );
}

Using aspect.around, but checking for methods calling each other

I would like to run some specific code around put() and add() for Dojo stores.
The problem I am having is that for JSON REST stores, in JsonRest.js add() is just a function that calls put():
add: function(object, options){
options = options || {};
options.overwrite = false;
return this.put(object, options);
},
So, if I use aspect.around() with add(), my code ends up being executed twice IF I apply my code to stores created with a store that implements add() as a stub to put().
Please note that I realise that most stores will do that. I just want my solution to be guaranteed to work with any store, whether there is method nesting or not.
Dojo's own Observable.js has the same problem. This is how they deal with it:
function whenFinished(method, action){
var original = store[method];
if(original){
store[method] = function(value){
if(inMethod){
// if one method calls another (like add() calling put()) we don't want two events
return original.apply(this, arguments);
}
inMethod = true;
try{
var results = original.apply(this, arguments);
Deferred.when(results, function(results){
action((typeof results == "object" && results) || value);
});
return results;
}finally{
inMethod = false;
}
};
}
}
// monitor for updates by listening to these methods
whenFinished("put", function(object){
store.notify(object, store.getIdentity(object));
});
whenFinished("add", function(object){
store.notify(object);
});
whenFinished("remove", function(id){
store.notify(undefined, id);
});
My question is: is there a simple, "short" way to change my existing code so that it checks if it's within a method, and avoid running the code twice?
I gave it a go, but I ended up with clanky, hacky code. I am sure I am missing something...
Here is my existing code:
topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){
aspect.around( store, 'put', function( put ){
return function( object, options ){
return when( put.call( store, object, options ) ).then( function( r ) {
var eventName;
var identity = store.idProperty;
eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';
topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );
} );
}
});
aspect.around( store, 'add', function( add ){
return function( object, options ){
return when( add.call( store, object, options ) ).then( function( r ) {
var identity = store.idProperty;
topic.publish('storeRecordCreate', null, { storeName: storeName, storeTarget: storeTarget, objectId: r[identity], object: object }, false } );
});
}
});
});
This is my attempt...
What I don't really "get" about my attempt is whether it's 100% safe or not.
If store.add() is called twice in a row, is is ever possible that inMethod is set to true by the first call, and that the second add() call then finds it already set to true because the first one hasn't managed to set it to false yet?
This would only really be possible if nextTick() is called in between the two calls I assume?
Or am I just completely confused by it all? (Which is very possible...)
topic.subscribe( 'hotplate/hotDojoStores/newStore', function( storeName, store ){
var inMethod;
aspect.around( store, 'put', function( put ){
return function( object, options ){
if( inMethod ){
return when( put.call( store, object, options ) );
} else {
inMethod = true;
try {
return when( put.call( store, object, options ) ).then( function( r ) {
var eventName;
var identity = store.idProperty;
eventName = object[ identity ] ? 'storeRecordUpdate' : 'storeRecordCreate';
topic.publish( eventName, null, { type: eventName, storeName: storeName, objectId: r[ identity ], object: object }, false );
});
} finally {
inMethod = false;
}
}
}
});
aspect.around( store, 'add', function( add ){
return function( object, options ){
if( inMethod ){
return when( add.call( store, object, options ) );
} else {
inMethod = true;
try {
return when( add.call( store, object, options ) ).then( function( r ) {
var identity = store.idProperty;
topic.publish('storeRecordCreate', null, { type: 'storeRecordCreate', storeName: storeName, objectId: r[identity], object: object }, false );
});
} finally {
inMethod = false;
}
}
}
});
aspect.around( store, 'remove', function( remove ){
return function( objectId, options ){
return when( remove.call( store, objectId, options ) ).then( function( r ) {
topic.publish('storeRecordRemove', null, { type: 'storeRecordRemove', storeName: storeName, objectId: objectId }, false );
});
};
});
});

How to bind dynamic function to dynamic event

I have a simple jQuery function with optional parameters, All I want is to pass control name, event name and value or function name than bind it to the passed control.
jQuery Function
BindEvents: function (options) {
var defaults = {
Control: null,
Events: null
}
settings = $.extend({}, defaults, options);
if (Events != null) {
$(Control).on(Events['name'], function () {
'How to call passed function';
});
}
}
Calling the Function
$.fn.BindEvents({
Control: "#txtTest",
Events: { "name": "focus", "value": "$.fn.test()" }
});
1) You forgot to use 'settings' prefix:
BindEvents: function (options) {
// defaults do not make sense this way, but they probably have a sane default in your code, right?
var defaults = {
Control: null,
Events: null
}
settings = $.extend({}, defaults, options);
if (settings.Events != null) {
$.each(settings.Events, function(eventName, handler) {
$(settings.Control).on(eventName, handler);
});
}
}
2) call BindEvents with an function reference instead of a string. I also changed the Event object a bit:
$.fn.BindEvents({
Control: "#txtTest",
Events: { "focus": $.fn.test }
});

Jquery AutoComplete with inherited object paramater

I am using jquery-1.9.0 and jquery-ui-1.10.0
var opts = {
source: availableTags
};
var optsA = Object.create(opts,
{
select: {
value: function (event, ui) {}
}
});
var optsB = Object.create(opts,
{
select: {
value: function (event, ui) {}
}
});
$("#tags1").autocomplete(
optsB
);
$("#tags2").autocomplete(
optsA
);
I am trying to build up two seperate arguments lists for my autocompletes. The objects seem to be constructed correctly but the autocomplete doesnt seem to recognise my definition for the select in the inherited objects.
There's a missing property in the Object.create invocation.
You should add enumerable: true
var opts = {
source: availableTags
};
var optsA = Object.create(opts,
{
select: {
value: function (event, ui) {},
enumerable: true
}
});
var optsB = Object.create(opts,
{
select: {
value: function (event, ui) {},
enumerable: true
}
});
$("#tags1").autocomplete(
optsB
);
$("#tags2").autocomplete(
optsA
);
The problem is that the for in loop in jQuery's core extend method can't find the select property because it's not marked as enumerable so that's why it's not accessible from inside the autocomplete.
JSFiddle

How To Refactor If-else Code Segment?

I'm developing win8(metro style) application with Html5-js-jquery.
I have this code segment;
GetBoutiqueDetail: function (boutiqueId, options) {
if (IsUserLogin()) {
//different job A
} else {
ShowLoginPanel(undefined);
}
},
GetProductDetail: function (boutiqueId, productId, options) {
if (IsUserLogin()) {
//different job B
} else {
ShowLoginPanel(undefined);
}
},
AddBasket: function (productId, productVariantId, quantity, options) {
if (IsUserLogin()) {
//different job C
} else {
ShowLoginPanel(undefined);
}
},....
.And ~20 functions should check if user login or not.
I should call functions like similar to "Library.GetBoutiqueDetail();"
So my question is simple, how can I refactor that code to remove these if-else sections ?
Try something like this:
checkLogin: function( action, actionArgs ) {
if( IsLogin ) {
return action.apply(this, actionArgs );
}
ShowLoginPanel();
},
GetBoutiqueDetail: function (boutiqueId, options) {
//different job A
},
GetProductDetail: function (boutiqueId, productId, options) {
//different job B
},
AddBasket: function (productId, productVariantId, quantity, options) {
//different job C
}
How about an object map for this:
var objMap = {
"GetBoutiqueDetail":fnJobA,
"GetProductDetail":fnJobB,
"AddBasket":fnJobC}
....
}
if (loggedIn) {
objMap[task]();
}
else {
doLogin();
}
You could always wrap the common code into a higher-scope function, and have it invoked from the library functions - e.g:
//Higher scope:
function CheckForLogin(executionFunction)
{
if(IsLogin) {
executionFunction();
} else {
ShowLoginPanel(undefined);
}
};
GetBoutiqueDetail: function (boutiqueId, options) {
CheckForLogin(//different job A)
}
Passing in different job 'N' as an anonymous function to CheckForLogin
In Javascript you can return from a function to end it, so f.ex:
GetProductDetail: function (boutiqueId, productId, options) {
if (!IsLogin) return ShowLoginPanel();
// different job...
}
You will still have some repetitive code though. Another option is to define a higher level function. Something like:
var loginOrAction = function() {
if (!IsLogin) return ShowLoginPanel();
var args = [].slice.call(arguments);
Library[args.shift()].apply(Library, args);
}
loginOrAction('GetBoutiqueDetail', boutiqueId, options);
use the ternary operator
(IsLogin) ? jobA() : ShowLoginPanel(undefined)

Categories