Related
I am working on a language that transpiles to javascript and has a similar syntax. However I want to include some new type of block statements. For syntax purposes they are the same as an IfStatement. How can I get esprima or acorn to parse this program MyStatement {a=1;} without throwing an error? Its fine if it calls it an IfStatement. I would prefer not to fork esprima.
It turns out, that the plugin capabilities of acorn are not really documented. It seems like forking acorn would be the easiest route. In this case, it is as simple as searching for occurances of _if and following a similar pattern for _MyStatement.
However it is possible to write a plugin to accomplish what I was trying to do. It seems a bit of a hack, but here is the code. The basic steps are:
To exend Parse and add to the list of keywords that will be recognized by the first pass
Create a TokenType for the new keyword and add it to the Parser.acorn.keywordTypes, extend parseStatement so that it processes the new TokenType
Create a handler for the new TokenType which will add information to the Abstract Syntax Tree as required by the keyword functionality and also consume tokens using commands like this.expect(tt.parenR) to eat a '(' or this.parseExpression() to process an entire expression.
Here is the code:
var program =
`
MyStatement {
MyStatement(true) {
MyStatement() {
var a = 1;
}
}
if (1) {
var c = 0;
}
}
`;
const acorn = require("acorn");
const Parser = acorn.Parser;
const tt = acorn.tokTypes; //used to access standard token types like "("
const TokenType = acorn.TokenType; //used to create new types of Tokens.
//add a new keyword to Acorn.
Parser.acorn.keywordTypes["MyStatement"] = new TokenType("MyStatement",{keyword: "MyStatement"});
//const isIdentifierStart = acorn.isIdentifierStart;
function wordsRegexp(words) {
return new RegExp("^(?:" + words.replace(/ /g, "|") + ")$")
}
var bruceware = function(Parser) {
return class extends Parser {
parse(program) {
console.log("hooking parse.");
//it appears it is necessary to add keywords here also.
var newKeywords = "break case catch continue debugger default do else finally for function if return switch throw try var while with null true false instanceof typeof void delete new in this const class extends export import super";
newKeywords += " MyStatement";
this.keywords = wordsRegexp(newKeywords);
return(super.parse(program));
}
parseStatement(context, topLevel, exports) {
var starttype = this.type;
console.log("!!!hooking parseStatement", starttype);
if (starttype == Parser.acorn.keywordTypes["MyStatement"]) {
console.log("Parse MyStatement");
var node = this.startNode();
return this.parseMyStatement(node);
}
else {
return(super.parseStatement(context, topLevel, exports));
}
}
parseMyStatement(node) {
console.log("parse MyStatement");
this.next();
//In my language, MyStatement doesn't have to have a parameter. It could be called as `MyStatement { ... }`
if (this.type == tt.parenL) {
node.test = this.parseOptionalParenExpression();
}
else {
node.test = 0; //If there is no test, just make it 0 for now (note that this may break code generation later).
}
node.isMyStatement = true; //set a flag so we know that this if a "MyStatement" instead of an if statement.
//process the body of the block just like a normal if statement for now.
// allow function declarations in branches, but only in non-strict mode
node.consequent = this.parseStatement("if");
//node.alternate = this.eat(acornTypes["else"]) ? this.parseStatement("if") : null;
return this.finishNode(node, "IfStatement")
};
//In my language, MyStatement, optionally has a parameter. It can also by called as MyStatement() { ... }
parseOptionalParenExpression() {
this.expect(tt.parenL);
//see what type it is
console.log("Type: ", this.type);
//allow it to be blank.
var val = 0; //for now just make the condition 0. Note that this may break code generation later.
if (this.type == tt.parenR) {
this.expect(tt.parenR);
}
else {
val = this.parseExpression();
this.expect(tt.parenR);
}
return val
};
}
}
process.stdout.write('\033c'); //cls
var result2 = Parser.extend(bruceware).parse(program); //attempt to parse
console.log(JSON.stringify(result2,null,' ')); //show the results.
I want to use Proxy on a customized class called ObservableList which contains an Array. Since Proxy is available only after ES6, I wonder if there is any alternative implementation.
My requirement is to get updated (rather than get noticed) for observers once ObservableList changes, so that the observers are always consist with observable with some filtering or mapping method.
var activities = new ObservableList(['reading', 'swimming']);
var sAct = activities.filter(function(v) {
return v[0] === 's';
});
// expect sAct.list to be ['swimming']
var meAct = activities.map(function(v) {
return 'I am ' + v;
});
// expect meAct.list to be ['I am reading', 'I am swimming']
activities.list.push('smiling');
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'smiling']
// expect meAct.list to be ['I am reading', 'I am swimming', 'I am smiling']
activities.list[1] = 'snoopying';
console.log(sAct.list, meAct.list);
// expect sAct.list to be ['swimming', 'snoopying']
// expect meAct.list to be ['I am reading', 'I am snoopying', 'I am smiling']
My implementation with Proxy is available at https://jsfiddle.net/ovilia/tLmbptr0/3/
used defineProperty.
not exactly with what you want. i just implemented a "reactive array". but i think it maybe works in your problems.
bad parts:
defined tons of getters/setters on the target.
accessing indexers not defined will be not reactive.
update() is to-be optimized.
good parts:
ES5 friendly.
if no indexers needed, use set(i, val)/get(i) will be reactive.
https://jsfiddle.net/jimnox/jrtq40p7/2/
As described in questions, I only need ObservableList to contain an Array, rather than to extend it, as Jim did in his complicated answer. And surprisingly enough, I found this could be easily achieved by wrapping the original Array operations.
One limitation is that index operation is not reactive in my implementation, in that I failed to find a proper way to capture index operations. If you have a better idea, feel welcomed to tell me! XD
Here's the full implementation.
export class ObservableList {
list: Array<any>;
private _observer: Array<ObserverList>;
constructor(list?: Array<any>) {
this.list = list || [];
this._initList();
this._initMethods();
this._observer = [];
}
notify(): void {
for (let o of this._observer) {
o.update();
}
}
private _initList(): void {
var that = this;
var operations = ['push', 'pop', 'shift', 'unshift', 'splice',
'sort', 'reverse'];
for (let operation of operations) {
this.list[operation] = function() {
var res = Array.prototype[operation].apply(that.list, arguments);
that.notify();
return res;
}
}
}
private _initMethods(): void {
var that = this;
var methods = ['filter', 'map'];
for (let method of methods) {
this[method] = (formatter: Function): ObserverList => {
var observer = new ObserverList(that, formatter, method);
this._observer.push(observer);
return observer;
}
}
}
}
export class ObserverList {
public list: Array<any>;
constructor(public observable: ObservableList,
public formatter: Function,
public method: string) {
this.list = [];
this.update();
}
update(): void {
var list = [];
var master = this.observable.list;
for (var i = 0, len = master.length; i < len; ++i) {
if (this.method === 'filter') {
if (this.formatter(master[i])) {
list.push(master[i]);
}
} else if (this.method === 'map') {
list.push(this.formatter(master[i]));
} else {
console.error('Illegal method ' + this.method + '.');
}
}
this.list = list;
}
}
Is using proxies a hard requirement? I wouldn't recommend proxies for
general programming tasks as you can end up with unpredictable and
hard-to-spot side effects.
If you keep to data and functions to transform it, avoiding mutable
state where possible, I think you'll end up with simpler code that's
easier to maintain.
var activities = ['reading', 'swimming'];
var sfilter = function(activities){
return activities.filter(function(v){
return v[0] === 's';
});
};
console.log(sfilter(activities));
var memap = function(activities){
return activities.map(function(v){
return 'I am ' + v;
});
};
console.log(memap(activities));
activities.push('smiling');
console.log(sfilter(activities));
console.log(memap(activities));
// Yes, I know this doesn't work in quite the same way,
// but you're asking for trouble here since in your
// code you're appending to one list, but overwriting
// an element in the other.
activities[1] = 'snoopying';
console.log(sfilter(activities));
console.log(memap(activities));
Stick to a Single Source of Truth and observe that. With each copy you are multiplying state complexity. That will make debugging, testing, and extending the code difficult.
I would like to be able to copy an object keeping getter and setter functionality.
NOTE: This question is about angularjs but it may apply to many other frameworks as well.
Code available at: https://jsfiddle.net/vwb04d4f/2/
function out(str) {
document.getElementById('body').innerHTML += str + "<br>";
}
var SomeObj = function () {
var obj = {
_val: 0,
set val(value) {
out("Set: " + value);
this._val = value;
},
get val() {
return this._val;
}
}
return obj;
};
var sObj = new SomeObj();
sObj.val = 100;
var sCopy = angular.copy(sObj);
sCopy.val = 200;
out("Value:" + sCopy.val);
var sExt = angular.extend(sObj);
sExt.val = 300;
out("Value: " + sObj.val);
Output:
Set: 100
Value:200
Set: 300
Value: 300
Why "set val" is not triggered anymore after "angular.copy" ? As you can see, the value is correctly stored.
"angular.extend" keeps the references, so updating sExt will update sObj, which I don't want.
I'm copying an scope object before passing it into a controller (modal):
$modal.open({
animation: true,
templateUrl: '/html/settings.html',
controller: 'ModalInstanceCtrl',
backdrop: false,
resolve: {
config: function() {
var cfgcpy = {};
angular.copy($scope.config, cfgcpy);
return cfgcpy;
}
}
}).result.then(function(res){
ok_func(res);
close_func();
}, function() {
close_func();
});
angular.module('app').controller('ModalInstanceCtrl', function ($scope, $modalInstance, config) {
$scope.config = config;
...
});
Any ideas on how to copy sObj without loosing "set" and "get" and without keeping references?
** UPDATE:
As pointed by the link provided by RichS, it seems that the reason is that getter and setter properties are not enumerable and thus they are not copied over. This question is closely related (or duplicated if we go to the source of the problem): Copy object with results of getters
I updated the code: https://jsfiddle.net/vwb04d4f/3/
I added manually the "enumerable" property:
var SomeObj = function () {
var obj = {
_val: 0
}
Object.defineProperty(obj, "val", {
enumerable: true,
set : function(value) {
out("Set: " + value);
this._val = value;
},
get: function(){
return this._val;
}
});
return obj;
};
However neither extend (from empty object) or copy do actually the job. Perhaps am I missing something?
** UPDATE 2 **
As this problem is not just related to angularjs.
I found a solution in this question: What is the most efficient way to deep clone an object in JavaScript?
function cloneObject(source) {
var key,value;
var clone = Object.create(source);
for (key in source) {
if (source.hasOwnProperty(key) === true) {
value = source[key];
if (value!==null && typeof value==="object") {
clone[key] = cloneObject(value);
} else {
clone[key] = value;
}
}
}
return clone;
}
See the updated code here:https://jsfiddle.net/vwb04d4f/6/
As you can see, "enumeration" is not required. So far this code seems to have solved my problem. Thanks goes to Steven Vachon.
There are plenty of solutions in that question, I tested most of them but not all.
it's happening because of deep/shallow:
"A lazy copy is a combination of both shallow copy and deep copy. When initially copying an object, a (fast) shallow copy is used. A counter is also used to track how many objects share the data."
Please read this or check this answer to make it more clear or even angular docs
I am looking for an efficient way to translate my Ember object to a json string, to use it in a websocket message below
/*
* Model
*/
App.node = Ember.Object.extend({
name: 'theName',
type: 'theType',
value: 'theValue',
})
The websocket method:
App.io.emit('node', {node: hash});
hash should be the json representation of the node. {name: thename, type: theType, ..}
There must be a fast onliner to do this.. I dont want to do it manualy since i have many attributes and they are likely to change..
As stated you can take inspiration from the ember-runtime/lib/core.js#inspect function to get the keys of an object, see http://jsfiddle.net/pangratz666/UUusD/
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, ret = [];
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
} // ignore useless items
if (Ember.typeOf(v) === 'function') {
continue;
}
ret.push(key);
}
}
return this.getProperties.apply(this, ret);
}
});
Note, since commit 1124005 - which is available in ember-latest.js and in the next release - you can pass the ret array directly to getProperties, so the return statement of the getJson function looks like this:
return this.getProperties(ret);
You can get a plain JS object (or hash) from an Ember.Object instance by calling getProperties() with a list of keys.
If you want it as a string, you can use JSON.stringify().
For example:
var obj = Ember.Object.create({firstName: 'Erik', lastName: 'Bryn', login: 'ebryn'}),
hash = obj.getProperties('firstName', 'lastName'), // => {firstName: 'Erik', lastName: 'Bryn'}
stringHash = JSON.stringify(hash); // => '{"firstName": "Erik", "lastName": "Bryn"}'
I have also been struggling with this. As Mirko says, if you pass the ember object to JSON.stringify you will get circular reference error. However if you store the object inside one property and use stringify on that object, it works, even nested subproperties.
var node = Ember.Object.create({
data: {
name: 'theName',
type: 'theType',
value: 'theValue'
}
});
console.log(JSON.stringify(node.get('data')));
However, this only works in Chrome, Safari and Firefox. In IE8 I get a stack overflow so this isn't a viable solution.
I have resorted to creating JSON schemas over my object models and written a recursive function to iterate over the objects using the properties in the schemas and then construct pure Javascript objects which I can then stringify and send to my server. I also use the schemas for validation so this solution works pretty well for me but if you have very large and dynamic data models this isn't possible. I'm also interested in simpler ways to accomplish this.
I modifed #pangratz solution slightly to make it handle nested hierarchies of Jsonables:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
App.io.emit('node', {node: node.toJSON()});
Or if you have an ID property and want to include it:
App.io.emit('node', {node: node.toJSON({includeId: true})});
Will this work for you?
var json = JSON.stringify( Ember.getMeta( App.node, 'values') );
The false is optional, but would be more performant if you do not intend to modify any of the properties, which is the case according to your question. This works for me, but I am wary that Ember.meta is a private method and may work differently or not even be available in future releases. (Although, it isn't immediately clear to me if Ember.getMeta() is private). You can view it in its latest source form here:
https://github.com/emberjs/ember.js/blob/master/packages/ember-metal/lib/utils.js
The values property contains only 'normal' properties. You can collect any cached, computed properties from Ember.meta( App.node, false ).cached. So, provided you use jQuery with your build, you can easily merge these two objects like so:
$.extend( {}, Ember.getMeta(App.node, 'values'), Ember.getMeta(App.node, 'cache') );
Sadly, I haven't found a way to get sub-structures like array properties in this manner.
I've written an extensive article on how you can convert ember models into native objects or JSON which may help you or others :)
http://pixelchild.com.au/post/44614363941/how-to-convert-ember-objects-to-json
http://byronsalau.com/blog/convert-ember-objects-to-json/
I modified #Kevin-pauli solution to make it works with arrays as well:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
v = this[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I also made some further modification to get the best of both worlds. With the following version I check if the Jsonable object has a specific property that informs me on which of its properties should be serialized:
App.Jsonable = Ember.Mixin.create({
getJson: function() {
var v, json = {}, base, inspectArray = function (aSome) {
if (Ember.typeof(aSome) === 'array') {
return aSome.map(inspectArray);
}
if (Jsonable.detect(aSome)) {
return aSome.getJson();
}
return aSome;
};
if (!Ember.isNone(this.get('jsonProperties'))) {
// the object has a selective list of properties to inspect
base = this.getProperties(this.get('jsonProperties'));
} else {
// no list given: let's use all the properties
base = this;
}
for (var key in base) {
if (base.hasOwnProperty(key)) {
v = base[key];
if (v === 'toString') {
continue;
}
if (Ember.typeOf(v) === 'function') {
continue;
}
if (Ember.typeOf(v) === 'array') {
v = v.map(inspectArray);
}
if (App.Jsonable.detect(v))
v = v.getJson();
json[key] = v;
}
}
return json;
}
});
I am using this little tweak and I am happy with it. I hope it'll help others as well!
Thanks to #pangratz and #Kevin-Pauli for their solution!
Here I take #leo, #pangratz and #kevin-pauli solution a little step further. Now it iterates not only with arrays but also through has many relationships, it doesn't check if a value has the type Array but it calls the isArray function defined in Ember's API.
Coffeescript
App.Jsonable = Em.Mixin.create
getJson: ->
jsonValue = (attr) ->
return attr.map(jsonValue) if Em.isArray(attr)
return attr.getJson() if App.Jsonable.detect(attr)
attr
base =
if Em.isNone(#get('jsonProperties'))
# no list given: let's use all the properties
this
else
# the object has a selective list of properties to inspect
#getProperties(#get('jsonProperties'))
hash = {}
for own key, value of base
continue if value is 'toString' or Em.typeOf(value) is 'function'
json[key] = jsonValue(value)
json
Javascript
var hasProp = {}.hasOwnProperty;
App.Jsonable = Em.Mixin.create({
getJson: function() {
var base, hash, hashValue, key, value;
jsonValue = function(attr) {
if (Em.isArray(attr)) {
return attr.map(jsonValue);
}
if (App.Jsonable.detect(attr)) {
return attr.getJson();
}
return attr;
};
base = Em.isNone(this.get('jsonProperties')) ? this : this.getProperties(this.get('jsonProperties'));
json = {};
for (key in base) {
if (!hasProp.call(base, key)) continue;
value = base[key];
if (value === 'toString' || Em.typeOf(value) === 'function') {
continue;
}
json[key] = jsonValue(value);
}
return json;
}
});
Ember Data Model's object counts with a toJSON method which optionally receives an plain object with includeId property used to convert an Ember Data Model into a JSON with the properties of the model.
https://api.emberjs.com/ember-data/2.10/classes/DS.Model/methods/toJSON?anchor=toJSON
You can use it as follows:
const objects = models.map((model) => model.toJSON({ includeId: true }));
Hope it helps. Enjoy!
I have:
fixed and simplified code
added circular reference prevention
added use of get of value
removed all of the default properties of an empty component
//Modified by Shimon Doodkin
//Based on answers of: #leo, #pangratz, #kevin-pauli, #Klaus
//http://stackoverflow.com/questions/8669340
App.Jsonable = Em.Mixin.create({
getJson : function (keysToSkip, visited) {
//getJson() called with no arguments,
// they are to pass on values during recursion.
if (!keysToSkip)
keysToSkip = Object.keys(Ember.Component.create());
if (!visited)
visited = [];
visited.push(this);
var getIsFunction;
var jsonValue = function (attr, key, obj) {
if (Em.isArray(attr))
return attr.map(jsonValue);
if (App.Jsonable.detect(attr))
return attr.getJson(keysToSkip, visited);
return getIsFunction?obj.get(key):attr;
};
var base;
if (!Em.isNone(this.get('jsonProperties')))
base = this.getProperties(this.get('jsonProperties'));
else
base = this;
getIsFunction=Em.typeOf(base.get) === 'function';
var json = {};
var hasProp = Object.prototype.hasOwnProperty;
for (var key in base) {
if (!hasProp.call(base, key) || keysToSkip.indexOf(key) != -1)
continue;
var value = base[key];
// there are usual circular references
// on keys: ownerView, controller, context === base
if ( value === base ||
value === 'toString' ||
Em.typeOf(value) === 'function')
continue;
// optional, works also without this,
// the rule above if value === base covers the usual case
if (visited.indexOf(value) != -1)
continue;
json[key] = jsonValue(value, key, base);
}
visited.pop();
return json;
}
});
/*
example:
DeliveryInfoInput = Ember.Object.extend(App.Jsonable,{
jsonProperties: ["title","value","name"], //Optionally specify properties for json
title:"",
value:"",
input:false,
textarea:false,
size:22,
rows:"",
name:"",
hint:""
})
*/
Ember.js appears to have a JSON library available. I hopped into a console (Firebug) on one the Todos example and the following worked for me:
hash = { test:4 }
JSON.stringify(hash)
So you should be able to just change your line to
App.io.emit('node', { node:JSON.stringify(hash) })
I've started to write few jQuery plugins and figured it'd be nice to setup my IDE with a jQuery plugin template.
I have been reading some articles and posts on this site related to plugin convention, design, etc.. and thought I'd try and consolidate all of that.
Below is my template, I am looking to use it frequently so was keen to ensure it generally conforms to jQuery plugin design convention and whether the idea of having multiple internal methods (or even its general design) would impact performance and be prone to memory issues.
(function($)
{
var PLUGIN_NAME = "myPlugin"; // TODO: Plugin name goes here.
var DEFAULT_OPTIONS =
{
// TODO: Default options for plugin.
};
var pluginInstanceIdCount = 0;
var I = function(/*HTMLElement*/ element)
{
return new Internal(element);
};
var Internal = function(/*HTMLElement*/ element)
{
this.$elem = $(element);
this.elem = element;
this.data = this.getData();
// Shorthand accessors to data entries:
this.id = this.data.id;
this.options = this.data.options;
};
/**
* Initialises the plugin.
*/
Internal.prototype.init = function(/*Object*/ customOptions)
{
var data = this.getData();
if (!data.initialised)
{
data.initialised = true;
data.options = $.extend(DEFAULT_OPTIONS, customOptions);
// TODO: Set default data plugin variables.
// TODO: Call custom internal methods to intialise your plugin.
}
};
/**
* Returns the data for relevant for this plugin
* while also setting the ID for this plugin instance
* if this is a new instance.
*/
Internal.prototype.getData = function()
{
if (!this.$elem.data(PLUGIN_NAME))
{
this.$elem.data(PLUGIN_NAME, {
id : pluginInstanceIdCount++,
initialised : false
});
}
return this.$elem.data(PLUGIN_NAME);
};
// TODO: Add additional internal methods here, e.g. Internal.prototype.<myPrivMethod> = function(){...}
/**
* Returns the event namespace for this widget.
* The returned namespace is unique for this widget
* since it could bind listeners to other elements
* on the page or the window.
*/
Internal.prototype.getEventNs = function(/*boolean*/ includeDot)
{
return (includeDot !== false ? "." : "") + PLUGIN_NAME + "_" + this.id;
};
/**
* Removes all event listeners, data and
* HTML elements automatically created.
*/
Internal.prototype.destroy = function()
{
this.$elem.unbind(this.getEventNs());
this.$elem.removeData(PLUGIN_NAME);
// TODO: Unbind listeners attached to other elements of the page and window.
};
var publicMethods =
{
init : function(/*Object*/ customOptions)
{
return this.each(function()
{
I(this).init(customOptions);
});
},
destroy : function()
{
return this.each(function()
{
I(this).destroy();
});
}
// TODO: Add additional public methods here.
};
$.fn[PLUGIN_NAME] = function(/*String|Object*/ methodOrOptions)
{
if (!methodOrOptions || typeof methodOrOptions == "object")
{
return publicMethods.init.call(this, methodOrOptions);
}
else if (publicMethods[methodOrOptions])
{
var args = Array.prototype.slice.call(arguments, 1);
return publicMethods[methodOrOptions].apply(this, args);
}
else
{
$.error("Method '" + methodOrOptions + "' doesn't exist for " + PLUGIN_NAME + " plugin");
}
};
})(jQuery);
Thanks in advance.
A while back I've build a plugin generator based on a blog article I have read: http://jsfiddle.net/KeesCBakker/QkPBF/. It might be of use. It is fairly basic and straight forward. Any comments would be very welcome.
You can fork your own generator and change it to your needs.
Ps. This is the generated body:
(function($){
//My description
function MyPluginClassName(el, options) {
//Defaults:
this.defaults = {
defaultStringSetting: 'Hello World',
defaultIntSetting: 1
};
//Extending options:
this.opts = $.extend({}, this.defaults, options);
//Privates:
this.$el = $(el);
}
// Separate functionality from object creation
MyPluginClassName.prototype = {
init: function() {
var _this = this;
},
//My method description
myMethod: function() {
var _this = this;
}
};
// The actual plugin
$.fn.myPluginClassName = function(options) {
if(this.length) {
this.each(function() {
var rev = new MyPluginClassName(this, options);
rev.init();
$(this).data('myPluginClassName', rev);
});
}
};
})(jQuery);
[Edit] 7 months later
Quoting from the github project
jQuery is no good, and jQuery plugins is not how do modular code.
Seriously "jQuery plugins" are not a sound architecture strategy. Writing code with a hard dependency on jQuery is also silly.
[Original]
Since I gave critique about this template I will propose an alternative.
To make live easier this relies on jQuery 1.6+ and ES5 (use the ES5 Shim).
I've spend some time re-designing the plugin template you've given and rolled out my own.
Links:
Github
Documentation
Unit tests Confirmed to pass in FF4, Chrome and IE9 (IE8 & OP11 dies. known bug).
Annotated Source Code
The PlaceKitten example plugin
Comparison:
I've refactored the template so that it's split into boilerplate (85%) and scaffolding code (15%). The intention is that you only have to edit the scaffolding code and you can keep leave boilerplate code untouched. To achieve this I've used
inheritance var self = Object.create(Base) Rather then editing the Internal class you have directly you should be editing a sub class. All your template / default functionality should be in a base class (called Base in my code).
convention self[PLUGIN_NAME] = main; By convention the plugin defined on jQuery will call the method define on self[PLUGIN_NAME] by default. This is considered the main plugin method and has a seperate external method for clarity.
monkey patching $.fn.bind = function _bind ... Use of monkey patching means that the event namespacing is done automatically for you under the hood. This functionality is free and does not come at the cost of readability (calling getEventNS all the time).
OO Techniques
It's better to stick to proper JavaScript OO rather then classical OO emulation. To achieve this you should use Object.create. (which ES5 just use the shim to upgrade old browsers).
var Base = (function _Base() {
var self = Object.create({});
/* ... */
return self;
})();
var Wrap = (function _Wrap() {
var self = Object.create(Base);
/* ... */
return self;
})();
var w = Object.create(Wrap);
This is different from the standard new and .prototype based OO people are used to. This approach is preferred because it re-inforces the concept that there are only Objects in JavaScript and it's a prototypical OO approach.
[getEventNs]
As mentioned this method has been refactored away by overriding .bind and .unbind to automatically inject namespaces. These methods are overwritten on the private version of jQuery $.sub(). The overwritten methods behave the same way as your namespacing does. It namespaces events uniquely based on plugin and instance of a plugin wrapper around a HTMLElement (Using .ns.
[getData]
This method has been replaced with a .data method that has the same API as jQuery.fn.data. The fact that it's the same API makes it easier to use, its basically a thin wrapper around jQuery.fn.data with namespacing. This allows you to set key/value pair data that is immediatley stored for that plugin only. Multiple plugins can use this method in parallel without any conflicts.
[publicMethods]
The publicMethods object has been replaced by any method being defined on Wrap being automatically public. You can call any method on a Wrapped object directly but you do not actually have access to the wrapped object.
[$.fn[PLUGIN_NAME]]
This has been refactored so it exposes a more standardized API. This api is
$(selector).PLUGIN_NAME("methodName", {/* object hash */}); // OR
$(selector).PLUGIN_NAME({/* object hash */}); // methodName defaults to PLUGIN_NAME
the elements in the selector are automatically wrapped in the Wrap object, the method is called or each selected element from the selector and the return value is always a $.Deferred element.
This standardizes the API and the return type. You can then call .then on the returned deferred to get out the actual data you care about. The use of deferred here is very powerful for abstraction away whether the plugin is synchronous or asynchronous.
_create
A caching create function has been added. This is called to turn a HTMLElement into a Wrapped element and each HTMLElement will only be wrapped once. This caching gives you a solid reduction in memory.
$.PLUGIN_NAME
Added another public method for the plugin (A total of two!).
$.PLUGIN_NAME(elem, "methodName", {/* options */});
$.PLUGIN_NAME([elem, elem2, ...], "methodName", {/* options */});
$.PLUGIN_NAME("methodName", {
elem: elem, /* [elem, elem2, ...] */
cb: function() { /* success callback */ }
/* further options */
});
All parameters are optional. elem defaults to <body>, "methodName" defaults to "PLUGIN_NAME" and {/* options */} defaults to {}.
This API is very flexible (with 14 method overloads!) and standard enough to get used to the syntnax for every method your plugin will expose.
Public exposure
The Wrap, create and $ objects are exposed globally. This will allow advanced plugin users maximum flexibility with your plugin. They can use create and the modified subbed $ in their development and they can also monkey patch Wrap. This allows for i.e. hooking into your plugin methods. All three of these are marked with a _ in front of their name so they are internal and using them breaks the garantuee that your plugin works.
The internal defaults object is also exposed as $.PLUGIN_NAME.global. This allows users to override your defaults and set plugin global defaults. In this plugin setup all hashes past into methods as objects are merged with the defaults, so this allows users to set global defaults for all your methods.
Actual Code
(function($, jQuery, window, document, undefined) {
var PLUGIN_NAME = "Identity";
// default options hash.
var defaults = {
// TODO: Add defaults
};
// -------------------------------
// -------- BOILERPLATE ----------
// -------------------------------
var toString = Object.prototype.toString,
// uid for elements
uuid = 0,
Wrap, Base, create, main;
(function _boilerplate() {
// over-ride bind so it uses a namespace by default
// namespace is PLUGIN_NAME_<uid>
$.fn.bind = function _bind(type, data, fn, nsKey) {
if (typeof type === "object") {
for (var key in type) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.bind(nsKey, data, type[key], fn);
}
return this;
}
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.bind.call(this, nsKey, data, fn);
};
// override unbind so it uses a namespace by default.
// add new override. .unbind() with 0 arguments unbinds all methods
// for that element for this plugin. i.e. calls .unbind(_ns)
$.fn.unbind = function _unbind(type, fn, nsKey) {
// Handle object literals
if ( typeof type === "object" && !type.preventDefault ) {
for ( var key in type ) {
nsKey = key + this.data(PLUGIN_NAME)._ns;
this.unbind(nsKey, type[key]);
}
} else if (arguments.length === 0) {
return jQuery.fn.unbind.call(this, this.data(PLUGIN_NAME)._ns);
} else {
nsKey = type + this.data(PLUGIN_NAME)._ns;
return jQuery.fn.unbind.call(this, nsKey, fn);
}
return this;
};
// Creates a new Wrapped element. This is cached. One wrapped element
// per HTMLElement. Uses data-PLUGIN_NAME-cache as key and
// creates one if not exists.
create = (function _cache_create() {
function _factory(elem) {
return Object.create(Wrap, {
"elem": {value: elem},
"$elem": {value: $(elem)},
"uid": {value: ++uuid}
});
}
var uid = 0;
var cache = {};
return function _cache(elem) {
var key = "";
for (var k in cache) {
if (cache[k].elem == elem) {
key = k;
break;
}
}
if (key === "") {
cache[PLUGIN_NAME + "_" + ++uid] = _factory(elem);
key = PLUGIN_NAME + "_" + uid;
}
return cache[key]._init();
};
}());
// Base object which every Wrap inherits from
Base = (function _Base() {
var self = Object.create({});
// destroy method. unbinds, removes data
self.destroy = function _destroy() {
if (this._alive) {
this.$elem.unbind();
this.$elem.removeData(PLUGIN_NAME);
this._alive = false;
}
};
// initializes the namespace and stores it on the elem.
self._init = function _init() {
if (!this._alive) {
this._ns = "." + PLUGIN_NAME + "_" + this.uid;
this.data("_ns", this._ns);
this._alive = true;
}
return this;
};
// returns data thats stored on the elem under the plugin.
self.data = function _data(name, value) {
var $elem = this.$elem, data;
if (name === undefined) {
return $elem.data(PLUGIN_NAME);
} else if (typeof name === "object") {
data = $elem.data(PLUGIN_NAME) || {};
for (var k in name) {
data[k] = name[k];
}
$elem.data(PLUGIN_NAME, data);
} else if (arguments.length === 1) {
return ($elem.data(PLUGIN_NAME) || {})[name];
} else {
data = $elem.data(PLUGIN_NAME) || {};
data[name] = value;
$elem.data(PLUGIN_NAME, data);
}
};
return self;
})();
// Call methods directly. $.PLUGIN_NAME(elem, "method", option_hash)
var methods = jQuery[PLUGIN_NAME] = function _methods(elem, op, hash) {
if (typeof elem === "string") {
hash = op || {};
op = elem;
elem = hash.elem;
} else if ((elem && elem.nodeType) || Array.isArray(elem)) {
if (typeof op !== "string") {
hash = op;
op = null;
}
} else {
hash = elem || {};
elem = hash.elem;
}
hash = hash || {}
op = op || PLUGIN_NAME;
elem = elem || document.body;
if (Array.isArray(elem)) {
var defs = elem.map(function(val) {
return create(val)[op](hash);
});
} else {
var defs = [create(elem)[op](hash)];
}
return $.when.apply($, defs).then(hash.cb);
};
// expose publicly.
Object.defineProperties(methods, {
"_Wrap": {
"get": function() { return Wrap; },
"set": function(v) { Wrap = v; }
},
"_create":{
value: create
},
"_$": {
value: $
},
"global": {
"get": function() { return defaults; },
"set": function(v) { defaults = v; }
}
});
// main plugin. $(selector).PLUGIN_NAME("method", option_hash)
jQuery.fn[PLUGIN_NAME] = function _main(op, hash) {
if (typeof op === "object" || !op) {
hash = op;
op = null;
}
op = op || PLUGIN_NAME;
hash = hash || {};
// map the elements to deferreds.
var defs = this.map(function _map() {
return create(this)[op](hash);
}).toArray();
// call the cb when were done and return the deffered.
return $.when.apply($, defs).then(hash.cb);
};
}());
// -------------------------------
// --------- YOUR CODE -----------
// -------------------------------
main = function _main(options) {
this.options = options = $.extend(true, defaults, options);
var def = $.Deferred();
// Identity returns this & the $elem.
// TODO: Replace with custom logic
def.resolve([this, this.elem]);
return def;
}
Wrap = (function() {
var self = Object.create(Base);
var $destroy = self.destroy;
self.destroy = function _destroy() {
delete this.options;
// custom destruction logic
// remove elements and other events / data not stored on .$elem
$destroy.apply(this, arguments);
};
// set the main PLUGIN_NAME method to be main.
self[PLUGIN_NAME] = main;
// TODO: Add custom logic for public methods
return self;
}());
})(jQuery.sub(), jQuery, this, document);
As can be seen the code your supposed to edit is below the YOUR CODE line. The Wrap object acts similarly to your Internal object.
The function main is the main function called with $.PLUGIN_NAME() or $(selector).PLUGIN_NAME() and should contain your main logic.
How about something like this ? It's much clearer but again it would be nice to hear from you if you can improve it without overcomplicating its simplicity.
// jQuery plugin Template
(function($){
$.myPlugin = function(options) { //or use "$.fn.myPlugin" or "$.myPlugin" to call it globaly directly from $.myPlugin();
var defaults = {
target: ".box",
buttons: "li a"
};
options = $.extend(defaults, options);
function logic(){
// ... code goes here
}
//DEFINE WHEN TO RUN THIS PLUGIN
$(window).on('load resize', function () { // Load and resize as example ... use whatever you like
logic();
});
// RETURN OBJECT FOR CHAINING
// return this;
// OR FOR FOR MULTIPLE OBJECTS
// return this.each(function() {
// // Your code ...
// });
};
})(jQuery);
// USE EXAMPLE with default settings
$.myPlugin(); // or run plugin with default settings like so.
// USE EXAMPLE with overwriten settings
var options = {
target: "div.box", // define custom options
buttons: ".something li a" // define custom options
}
$.myPlugin(options); //or run plugin with overwriten default settings
I've been googling and landed here so, I have to post some ideas: first I agree with #Raynos.
The most code out there that tries to build a jQuery plugin actually...is not a plugin! It's just an object stored in memory which is refered by the data property of a node/element. That's because jQuery should be seen and used as a tool side by side with a class library (to remedy js inconsistencies from OO architecture) to build better code and yes this is not bad at all!
If you don't like classical OO behaviour stick to a prototypal library like clone.
So what our options really?
use JQueryUI/Widget or a similar library that hides technicalities and
provides abstraction
don't use them because of complexities, learning curve and god knows future changes
don't use them becuase you want to insist on modular design, build small-increase later
don't use them because you might want porting/connecting your code with different libraries.
Suppose the issues addressed by the following scenario (see the complexities from this question: Which jQuery plugin design pattern should I use?):
we have nodes A, B and C that store an object reference into their data property
some of them store info in public and private accessible internal objects,
some classes of these objects are connected with inheritance,
all of these nodes also need some private and public singletons to work best.
What would we do? See the drawning:
classes : | A B C
------------------case 1----------
members | | | |
of | v v v
an object | var a=new A, b=new B, c=new C
at | B extends A
node X : | a, b, c : private
------------------case 2---------
members | | | |
of | v v v
an object | var aa=new A, bb=new B, cc=new C
at | BB extends AA
node Y : | aa, bb, cc : public
-------------------case 3--------
members | | | |
of | v v v
an object | var d= D.getInstance() (private),
at | e= E.getInstance() (public)
node Z : | D, E : Singletons
as you can see every node refers to an object - a jQuery approach - but these objects change wildely; they contain object-properties with different data stored in or, even singletons that should be...single in memory like the prototype functions of the objects. We don't want every object's function belonging to class A to be repeatedly duplicated in memory in every node's object!
Before my answer see a common approach I've seen in jQuery plugins - some of them very popular but I don't say names:
(function($, window, document, undefined){
var x = '...', y = '...', z = '...',
container, $container, options;
var myPlugin = (function(){ //<----the game is lost!
var defaults = {
};
function init(elem, options) {
container = elem;
$container = $(elem);
options = $.extend({}, defaults, options);
}
return {
pluginName: 'superPlugin',
init: function(elem, options) {
init(elem, options);
}
};
})();
//extend jquery
$.fn.superPlugin = function(options) {
return this.each(function() {
var obj = Object.create(myPlugin); //<---lose, lose, lose!
obj.init(this, options);
$(this).data(obj.pluginName, obj);
});
};
}(jQuery, window, document));
I was watching some slides at: http://www.slideshare.net/benalman/jquery-plugin-creation from Ben Alman where he refers at slide 13 to object literals as singletons and that just knock me over: this is what the above plugin does, it creates one singleton with no chance whatsover to alter it's internal state!!!
Furthermore, at the jQuery part it stores a common reference to every single node!
My solution uses a factory to keep internal state and return an object plus it can be expanded with a class library and split in different files:
;(function($, window, document, undefined){
var myPluginFactory = function(elem, options){
........
var modelState = {
options: null //collects data from user + default
};
........
function modeler(elem){
modelState.options.a = new $$.A(elem.href);
modelState.options.b = $$.B.getInstance();
};
........
return {
pluginName: 'myPlugin',
init: function(elem, options) {
init(elem, options);
},
get_a: function(){return modelState.options.a.href;},
get_b: function(){return modelState.options.b.toString();}
};
};
//extend jquery
$.fn.myPlugin = function(options) {
return this.each(function() {
var plugin = myPluginFactory(this, options);
$(this).data(plugin.pluginName, plugin);
});
};
}(jQuery, window, document));
My project: https://github.com/centurianii/jsplugin
See: http://jsfiddle.net/centurianii/s4J2H/1/