http://jsbin.com/ukizof/1/
how do you call a function which is part of a array and set a paramater to it , as in the example below i want the script to return a function in order to call a function parameter which as below in the example is set below.
var bQuery = {
one: function(elem, options) {
options = this.extend({
method: 'html',
event: 'test2',
func:null
}, options || {});
var element = elem;
if (options.method == 'html') {
element.innerHTML = options.event;
} else if (options.method == 'append') {
element.innerHTML = element.innerHTML + options.event;
} else if (options.method == 'prepend') {
element.innerHTML = options.event + element.innerHTML;
}
return // return method to apply string to func: parameter function as below is "e"
},
extend: function(a, b) {
for (var prop in b) {
a[prop] = b[prop];
}
return a;
}
};
$ = bQuery;
$.one(document.getElementById("log"), {
method: 'append',
event: 'rjfjfjjffj',
func: function(e){
alert(e);
}
});
If I understood you right, you want
var options, element;
…
return function() {
if (typeof options.func == 'function')
return options.func(element.innerHTML);
};
Related
I have code like this in my unit tests for jQuery Terminal:
// https://github.com/tmpvar/jsdom/issues/135
Object.defineProperties(window.HTMLElement.prototype, {
offsetLeft: {
get: function() { return parseFloat(window.getComputedStyle(this).marginLeft) || 0; }
},
offsetTop: {
get: function() { return parseFloat(window.getComputedStyle(this).marginTop) || 0; }
},
offsetHeight: {
get: function() { return parseFloat(window.getComputedStyle(this).height) || 0; }
},
offsetWidth: {
get: function() { return parseFloat(window.getComputedStyle(this).width) || 0; }
},
// this will test if setting 1ch change value to 1ch which don't work in jsdom used by jest
style: {
get: function() {
if (this.__style) {
return this.__style;
}
var self = this;
var attr = {};
function set_style_attr() {
var str = Object.keys(attr).map((key) => `${key}: ${attr[key]}`).join(';') + ';';
self.setAttribute('style', str);
}
var mapping = {
backgroundClip: 'background-clip',
className: 'class'
};
var reversed_mapping = {};
Object.keys(mapping).forEach(key => {
reversed_mapping[mapping[key]] = key;
});
return this.__style = new Proxy({}, {
set: function(target, name, value) {
name = mapping[name] || name;
if (!value) {
delete target[name];
delete attr[name];
} else {
attr[name] = target[name] = value;
}
set_style_attr();
return true;
},
get: function(target, name) {
if (name === 'setProperty') {
return function(name, value) {
attr[name] = target[name] = value;
set_style_attr();
};
} else {
return target[name];
}
},
deleteProperty: function(target, name) {
name = reversed_mapping[name] || name;
delete target[name];
delete attr[name];
set_style_attr();
}
});
}
}
});
It works for 1ch attribute in my tests that look like this:
it('should handle wider characters without formatting', function() {
var input = 'ターミナルウィンドウは黒[[;;]です]';
var string = $.terminal.format(input, {char_width: 7});
expect(string).toEqual('<span style="width: 24ch"><span style="widt'+
'h: 24ch">ターミナルウィンドウは黒</span></span'+
'><span style="width: 4ch" data-text="です">'+
'<span style="width: 4ch">です</span></span>');
});
If I don't use my Proxy I got width in pixels, because I have code like this to check if ch is supported in my code:
var agent = window.navigator.userAgent;
var is_IE = /MSIE|Trident/.test(agent) || /rv:11.0/i.test(agent);
var is_IEMobile = /IEMobile/.test(agent);
// -------------------------------------------------------------------------
var is_ch_unit_supported = (function() {
if (is_IE && !is_IEMobile) {
return false;
}
var div = document.createElement('div');
div.style.width = '1ch';
return div.style.width === '1ch';
})();
The problem is when I want to check the style property to get some value, like this test:
it('should find inside formatting', function() {
term.less(big_text.concat(['[[;red;]foo bar baz]']));
search('bar');
var spans = term.find('[data-index="0"] > div:first-child span');
['foo ', 'bar', ' baz'].forEach(function(string, i) {
expect(a0(spans.eq(i).text())).toEqual(string);
});
[true, false, true].forEach(function(check, i) {
console.log(spans.get(i).style.getPropertyValue('color'));
expect([i, !!spans.get(i).attr('style').match(/color:\s*red/)]).toEqual([i, check]);
});
});
I've tried:
spans.get(i).style.getPropertyValue('color')
This return error that's not a function and
spans.get(i).attr('style')
is undefined. This also don't work
spans.get(i).getAttribute('style')
which should be the same and the one before.
Is there a way to have ch unit support check work but in same way getting values from style attribute as well?
I'm using jest framework that use jsDom I running my tests from Node.
I've tried to create getPropertyValue function in get trap for the proxy but I don't know how to get original function so I can call it.
So to sum up, I need solution in jsDOM that allow to set width to 1ch and get that value back (so my code don't change), and that should work when creating new HTMLElement in DOM and get it's value out of, It don't need to be style object with props it can be style property as string. Alternative solution is to test if ch unit is supported that will work in jsDOM.
I've solved the issue by temporarily disabling getter when accessing properties:
(function() {
var style_descriptor = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'style');
Object.defineProperties(window.HTMLElement.prototype, {
offsetLeft: {
get: function() { return parseFloat(window.getComputedStyle(this).marginLeft) || 0; }
},
offsetTop: {
get: function() { return parseFloat(window.getComputedStyle(this).marginTop) || 0; }
},
offsetHeight: {
get: function() { return parseFloat(window.getComputedStyle(this).height) || 0; }
},
offsetWidth: {
get: function() { return parseFloat(window.getComputedStyle(this).width) || 0; }
},
// this will test if setting 1ch change value to 1ch which don't work in jsdom used by jest
style: {
get: function getter() {
if (this.__style) {
return this.__style;
}
var self = this;
var attr = {};
function set_style_attr() {
var str = Object.keys(attr).map((key) => `${key}: ${attr[key]}`).join(';') + ';';
self.setAttribute('style', str);
}
var mapping = {
backgroundClip: 'background-clip',
className: 'class'
};
var reversed_mapping = {};
Object.keys(mapping).forEach(key => {
reversed_mapping[mapping[key]] = key;
});
function disable(fn) {
// temporary disable proxy
Object.defineProperty(window.HTMLElement.prototype, "style", style_descriptor);
var ret = fn();
Object.defineProperty(window.HTMLElement.prototype, "style", {
get: getter
});
return ret;
}
return this.__style = new Proxy({}, {
set: function(target, name, value) {
name = mapping[name] || name;
if (!value) {
delete target[name];
delete attr[name];
} else {
attr[name] = target[name] = value;
}
set_style_attr();
disable(function() {
self.style[name] = name;
});
return true;
},
get: function(target, name) {
if (name === 'setProperty') {
return function(name, value) {
attr[name] = target[name] = value;
set_style_attr();
};
} else if (target[name]) {
return target[name];
} else {
return disable(function() {
return self.style[name];
});
}
},
deleteProperty: function(target, name) {
name = reversed_mapping[name] || name;
delete target[name];
delete attr[name];
disable(function() {
delete self.style[name];
});
set_style_attr();
}
});
}
}
});
})();
I am creating a pluging based in Jquery and Bootrap (Laste Versions), that is call by this method/code:
$(document).find('.calendar-plug-bs').each(function(index, el) {
//$(this).plgCalendar("destroy"); Testing for destroy if exist...
$(this).plgCalendar();
})
it work with and element like this:
<div class="form-group row">
<div class="calendar-plug-bs"></div>
</div>
and this script/plugin starts as follows ...
(function($) {
$.fn.plgCalendar = function(param) {
return window.plgCalendar(param);
};
}(jQuery));
But the problem is that i need retrive node that call the initial function...
|------|
$(this).plgCalendar();
to add a table inside it ...
function plgCalendar(param = null) {
var r = null;
if (param !== null) {
if (typeof param.func !== 'undefined') {
if (param.func === "destroy") {
} else if (param.func === "getValue") {
}
} else {
console.log('%cMSG: (func) not Set', 'color: #bada55');
}
} else {
/*****WORKING ON THIS***/
var target = $(this);
console.log(target);
if (target.is("div")) {
var id = window.BuildRandID();
var $tableObject = $('<table/>', {
'class': 'bigger',
'id': id
});
$(target).append($tableObject);
return id;
} else {
window.alert("calendar-plug-bs must be a div");
}
/*****WORKING ON THIS***/
}
return r;
}
the fail is that var taget not is a div... and i dont know how to retrive...
resolve by this way:
(function($) {
$.fn.plgCalendar = function(param = null) {
param = window.plgCalendarParam(param);
param['this'] = $(this);
return window.plgCalendar(param);
};
}(jQuery));
function plgCalendarParam(param) {
if (param == null) {
param = [];
}
return param;
}
function plgCalendar(param) {
var r = null;
if (typeof param.func !== 'undefined') {
if (param.func === "destroy") {
} else if (param.func === "getValue") {
}
} else {
/*****WORKING ON THIS***/
var target = param['this'];
if (target.is("div")) {
var id = 'plgCalendar_' + window.BuildRandID();
var $tableObject = $('<table/>', {
'class': 'bigger',
'id': id
});
$(target).append($tableObject);
r = { 'id': id, 'init': true };
} else {
window.alert("calendar-plug-bs must be a div");
}
/*****WORKING ON THIS***/
}
return r;
}
I made an object like this:
var MyObj = function(arg)
{
var PublicMethods = {
SomeMethod: function(someArg)
{
if(SomeCheck(arg)
{
PublicMethods.SomeFunc2 = somethingElse;
}
}
};
return PublicMethods;
};
However MyObj doesn't seem to be persistent and between calls, PublicMethods doesnt preserve the new methods added to it, so I tried moving it to global scope, however then it doesn't recognize the 'args' passed from MyObj anymore.
If I make MyObj like this:
var MyObj = (function()
{
//..
}());
Then it becomes a persistent object, but I'm not sure - can I call it like a function again?
jQuery seems to have a persistent object and at the same time it can be called like a function, how do they achieve that?
I want to be able to do this:
MyObj("Something Here").SomeMethod("Something Else");
and thus to be able to create a SomeFunc2 method that I can later call too:
MyObj("Something Here").SomeFunc2("Something Else");
Simply store the result of the initial MyObj call in a local variable:
var obj = MyObj("Something Here");
obj.SomeMethod("Something Else");
obj.SomeFunc2("Something else");
The PublicMethods variable is specific to each call of MyObj, so when you call MyObj for the second time, you get a different instance of PublicMethods. By using a variable to store the result of the first MyObj call, you can use the same instance of PublicMethods for both the SomeMethod and SomeFunc2 functions.
As a side note, you may want to look into constructor functions which would allow you to define functions more simply, instead of returning an object. For example:
function Example() {
this.a = function() {
return "a";
};
this.b = function() {
this.a = function() {
return "b";
}
}
}
var example = new Example();
example.a(); // => "a"
example.b();
example.a(); // => "b"
You can create a jQuery method which extends jQuery or jQuery.fn and can also set this context within the method.
(function($) {
jQuery.addMethod = function addMethod({methodName, method, type}) {
let bool = {
[type]: false
};
let _jQuery_jQueryFn_ = Object.keys(bool).pop();
if (type === "jQuery") {
for (let prop in jQuery) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "fn") {
for (let prop in jQuery.fn) {
if (prop === methodName
|| prop.toUpperCase() === methodName.toUpperCase()) {
bool[type] = true;
break;
}
}
}
if (type === "jQuery" && bool[_jQuery_jQueryFn_] === false) {
jQuery[methodName] = method;
}
if (type === "fn" && bool[_jQuery_jQueryFn_] === false) {
jQuery[type][methodName] = method;
}
if (bool[_jQuery_jQueryFn_] === true) {
return Promise.reject(
new ReferenceError(
methodName
+ " previously defined at "
+ _jQuery_jQueryFn_
));
} else {
console.log(methodName + " defined at " + _jQuery_jQueryFn_);
}
return {methodName:methodName, type};
}
})(jQuery);
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "jQuery"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery[type][methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve($.addMethod({
methodName: "add",
method: function add(a, b, context) {
console.log(a + b);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (methodName === "jQuery" && methodName in window[type]) {
jQuery[methodName](10, 10)
} else {
if (methodName in window["jQuery"][type]) {
jQuery("span")[methodName](10, 10);
}
}
})
.catch(function(err) {
console.error(err)
});
});
$(function() {
Promise.resolve(
$.addMethod({
methodName: "reverseText",
method: function reverseText(_text, context) {
let text = [...(_text || this.text())].reverse().join("");
(context || this).text(text);
return (context || this)
},
type: "fn"
}))
.then(function({methodName, type}) {
if (type === "jQuery" && methodName in window[type]) {
jQuery[methodName]()
} else {
if (methodName in window["jQuery"][type]) {
// set context `this` to `span`
let span = jQuery("section")[methodName]("321", $("span"))
.css("color", "sienna");
console.log(
span.is(document.querySelector("span"))
);
jQuery("section")[methodName]()
.css("color", "green");
}
}
})
.catch(function(err) {
console.error(err)
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js">
</script>
<section>section</section>
<span>span</span>
I'm starting to write jQuery in Vanilla JS and my selectors work but when I call my append function on the HTML element I get an "is not a function" error.
var $ = function(){
this.select = function(input) {
if (input.split("")[0] == "#") {
input = input.slice(1, input.length)
return document.getElementById(input)
}
else if (input.split("")[0] == ".") {
input = input.slice(1, input.length)
return document.getElementsByClassName(input)
}
else {
return document.getElementsByTagName(input)
}
},
this.append = function(text) {
return this.innerhtml = this.innerhtml + text
}
};
my console attempts:
var myQuery = new $();
returns undefined
myQuery.select("#testspan")
returns my span tag here
myQuery.select("#testspan").append("hellohello")
returns error
VM2207:1 Uncaught TypeError: myQuery.select(...).append is not a function(…)
From your snippet the return of each of the select method return a DOM element (or collection). Really what you would like to do is called Chaining where the result of the method returns the original object. Therefore you can keep calling additional methods on the same object.
Now in your example you are going to need a collection of elements (nodes) somewhere the object can then access again. Here is a simple example.
var $ = function () {
this.nodes = [];
this.select = function (input) {
var self = this;
if (input.split("")[0] == "#") {
input = input.slice(1, input.length)
var node = document.getElementById(input);
if (node)
this.nodes.push(node);
}
else if (input.split("")[0] == ".") {
input = input.slice(1, input.length)
Array.prototype.slice.call(document.getElementsByClassName(input), 0).forEach(function (node) {
self.nodes.push(node);
});
}
else {
Array.prototype.slice.call(document.getElementsByTagName(input), 0).forEach(function (node) {
self.nodes.push(node);
});
}
return this;
},
this.append = function (text) {
this.nodes.forEach(function (i) {
i.innerHTML += text;
});
return this;
}
};
Sample Html:
<p id="test">This is test </p>
<p>This is number to</p>
Console (Chrome):
$ = new $()
$ {nodes: Array[0]}
$.select('p').append('hi')
Now a little issue here is you are (in the console) setting $ = new $() which effectivly overwrites the ability to call new $() again in the same script. I have provided a fiddle below that renames this to myQuery. Also changed that every time you call select will clear the node array.
Revised:
var myQuery = function () {
this.nodes = [];
this.select = function (input) {
this.nodes = [];
var self = this;
if (input.split("")[0] == "#") {
input = input.slice(1, input.length)
var node = document.getElementById(input);
if (node)
this.nodes.push(node);
}
else if (input.split("")[0] == ".") {
input = input.slice(1, input.length)
Array.prototype.slice.call(document.getElementsByClassName(input), 0).forEach(function (node) {
self.nodes.push(node);
});
}
else {
Array.prototype.slice.call(document.getElementsByTagName(input), 0).forEach(function (node) {
self.nodes.push(node);
});
}
return this;
},
this.append = function (text) {
this.nodes.forEach(function (i) {
i.innerHTML += text;
});
return this;
}
};
$ = new myQuery();
$.select('p').append(' test selection by tag name ');
$ = new myQuery();
$.select('.p1').append(' test selection by class ');
$ = new myQuery();
$.select('#p1').append(' test selection by id ');
$ = new myQuery();
$.select('#p2').append(' test selection by id ').append('and then chanined').select('.p2').append(' still chaining');
Fiddle: https://jsfiddle.net/kxwt9gmg/
You need to change up your approach a bit. You are wanting to store a result and call a method on it. You can ONLY call a method that that particular object has. That object you are returning, the raw html element, doesn't have that method. What you want to do is store the html element and then return an OBJECT that performs operations on what was stored. You can accomplish this using closure. For example:
function miniQuery(input){
function elementIterate(collection, action){
for (var i = elements.length -1; i >= 0; i-- ){
collection[i].style.display = action;
}
}
var isCollection = function(element){
if(element instanceof HTMLCollection){
return true
} else{
return false
}
}
function findElement(element){
if (element.startsWith("#")) {
// id element selector
return document.getElementById(element.substring(1));
} else if (element.startsWith(".")) {
// class element selector
return document.getElementsByClassName(element.substring(1));
} else {
// tag element selector
return document.getElementsByTagName(element);
};
}
if (input != undefined) {
var _this = this;
this.element = findElement(input);
var elements = findElement(input);
}
return {
append: function(content, position = 'beforeend'){
var elements = _this.element;
if (isCollection(elements)) {
for(var i = elements.length -1; i >= 0; i--){
elements[i].insertAdjacentHTML(position, content)
}
}else{
elements.insertAdjacentHTML(position, content);
}
}
}
}
function $(input){
return selector(input);
}
function selector(input){
var query = new miniQuery(input);
return query;
}
I have this simple plugin I am building which just builds a table:
; (function ($, window, document, undefined) {
// Create the defaults once
var pluginName = "tableBuilder",
defaults = {
};
// The actual plugin constructor
function Plugin(element, options) {
this.element = element;
// jQuery has an extend method that merges the
// contents of two or more objects, storing the
// result in the first object. The first object
// is generally empty because we don't want to alter
// the default options for future instances of the plugin
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
String.prototype.format = function (values) {
var regex = /\{([\w-.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g;
var getValue = function (key) {
var value = values,
arr, type;
if (values == null || typeof values === 'undefined') return null;
if (key.indexOf('.')) {
arr = key.split('.');
while (arr.length && value) {
value = value[arr.shift()];
}
} else {
value = val && val[key] || values[key];
}
type = typeof value;
return type === 'string' || type === 'number' ? value : null;
};
return this.replace(regex, function (match) {
//match will look like {sample-match}
//key will be 'sample-match';
var key = match.substr(1, match.length - 2);
var value = getValue(key);
return value != null ? value : match;
});
};
Plugin.prototype = {
init: function () {
// Place initialization logic here
// You already have access to the DOM element and
// the options via the instance, e.g. this.element
// and this.options
// you can add more functions like the one below and
// call them like so: this.yourOtherFunction(this.element, this.options).
this.cycle();
},
cycle: function() {
var self = this;
self.buildRow();
self.display();
},
buildRow: function () {
var self = this;
self.rows = [];
$.each(self.options.json, function (i, item) {
self.rows.push(self.options.rowTemplate.format(item));
});
console.log(self.rows);
},
display: function (el, options) {
var self = this;
$(self.element).html(self.rows.join());
}
};
// A really lightweight plugin wrapper around the constructor,
// preventing against multiple instantiations
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new Plugin(this, options));
}
});
};
})(jQuery, window, document);
I call this from a button click event:
var row = "<tr data-id=\"{Id}\"><td>{FileName}</td><td>{Metadata.FileSize}</td><td></td><td><button type=\"button\" class=\"close\" data-id=\"{Id}\" aria-hidden=\"true\">×</button></td></tr>"
$("#assets").on("click", ".glyphicon", function () {
var $asset = $(this).parent();
var $actionBar = $("#action-bar");
var $selected = $("#selected-asset");
var $table = $(".table");
var currentSelected = parseInt($selected.text());
var assetId = parseInt($asset.attr("id"))
if ($asset.hasClass("active")) {
$selected.text(currentSelected - 1);
activeItems = $.grep(activeItems, function (obj) {
return obj.Id != assetId
});
$asset.removeClass("active");
if (activeItems.length <= 0) {
$actionBar.hide();
}
} else {
$selected.text(currentSelected + 1);
var asset = $.grep(assets, function (obj) {
return obj.Id == assetId
});
activeItems.push(asset[0]);
$asset.addClass("active");
$actionBar.show();
}
$("#assets-table").tableBuilder({
json: activeItems,
rowTemplate: row
});
});
Now, when I click add the first time, the table is created. But each click after does nothing. I put a console.log on the buildRows function and it only gets called once, which is expected because we only instantiated the plugin on that element.
So, I need to add a refresh function or an add/remove function that is available to the client.
Can anyone give me a hand?
Ok, so I was not so impressed with my last answer.
With the help of this video:
Head first into plugin development
I was able to work out that all the functions are actually part of the plugin instance.
So, here is my new plugin :)
String.prototype.format = function (values) {
var regex = /\{([\w-.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g;
var getValue = function (key) {
var value = values,
arr, type;
if (values == null || typeof values === 'undefined') return null;
if (key.indexOf('.')) {
arr = key.split('.');
while (arr.length && value) {
value = value[arr.shift()];
}
} else {
value = val && val[key] || values[key];
}
type = typeof value;
return type === 'string' || type === 'number' ? value : null;
};
return this.replace(regex, function (match) {
//match will look like {sample-match}
//key will be 'sample-match';
var key = match.substr(1, match.length - 2);
var value = getValue(key);
return value != null ? value : match;
});
};
; (function ($, window, document, undefined) {
var pluginName = "tableBuilder",
defaults = {
};
function Plugin(element, options) {
this.element = element;
this.$element = $(element);
this.rows = [];
this.rowTemplate = (typeof options === "string") ? options : options.rowTemplate;
this.options = $.extend({}, defaults, options);
this._defaults = defaults;
this._name = pluginName;
this.init();
}
Plugin.prototype = {
init: function () {
this.cycle();
},
cycle: function () {
var self = this;
if (self.options.json != null) {
self.buildRow();
self.display();
}
if (typeof self.options.onComplete === "function") {
self.options.onComplete.apply(self.element, arguments);
}
},
buildRow: function () {
var self = this;
$.each(self.options.json, function (i, item) {
self.rows.push(self.rowTemplate.format(item));
});
},
display: function (el, options) {
this.$element.html(this.rows.join());
},
add: function (row) {
console.log("moo");
this.rows.push(this.options.rowTemplate.format(row));
this.display();
},
remove: function(row) {
var match = this.options.rowTemplate.format(row);
this.rows = $.grep(this.rows, function (obj) {
return obj != match;
});
this.display();
}
};
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new Plugin(this, options));
}
});
};
})(jQuery, window, document);
now, the functions I needed access to are add() and remove() so if you look at these lines:
$.fn[pluginName] = function (options) {
return this.each(function () {
if (!$.data(this, "plugin_" + pluginName)) {
$.data(this, "plugin_" + pluginName,
new Plugin(this, options));
}
});
};
they are actually passing the instance to the $.data array which allows me to call my instance with a line of code:
$("#assets-table").data("plugin_tableBuilder")
and because of this, I am able to call any function that is a part of that instance, like this:
$("#assets-table").data("plugin_tableBuilder").add(asset[0]); // Add a row to our widget
I hope this helps someone else :D
/r3plica
I am going to answer this myself :)
Basically I decided that this was not the best way to handle my widget, so I used the widget factory boilerplate to sort out my issue. I modified my click event to this:
$("#assets").on("click", ".glyphicon", function () {
var $asset = $(this).parent(); // Get our asset element
var $actionBar = $("#action-bar"); // Get the action bar
var $selected = $("#selected-asset");// Get our selected asset counter
var currentSelected = parseInt($selected.text()); // Get our current counter value
var assetId = parseInt($asset.attr("id")); // Get the asset id
var asset = $.grep(assets, function (obj) { // Find our asset from our array
return obj.Id == assetId;
});
if ($asset.hasClass("active")) { // If our asset is already selected, then we must unselect it
$selected.text(currentSelected - 1); // First, decrease our counter
tableWidget.tableBuilder("remove", asset[0]); // Then call our widget and remove the current asset from the table
activeItems = $.grep(activeItems, function (obj) { // Repopulate our array of active assets
return obj != asset;
});
$asset.removeClass("active"); // And remove the active class from our element
if (activeItems.length <= 0) { // Finally, if this is the only selected asset
$actionBar.hide(); // Hide our actionbar
}
} else { // Else, we are selecting an asset
$selected.text(currentSelected + 1); // Increase our counter
tableWidget.tableBuilder("add", asset[0]); // Add a row to our widget
activeItems.push(asset[0]); // Add the asset to our array of active assets
$asset.addClass("active"); // Add our active alss to our element
$actionBar.show(); // And show our actionbar
}
});
And I instantiated my plugin on the page load like this:
var row = "<tr data-id=\"{Id}\"><td>{FileName}</td><td>{Metadata.FileSize}</td><td></td><td><button type=\"button\" class=\"close\" data-id=\"{Id}\" aria-hidden=\"true\">×</button></td></tr>"
var tableWidget;
$(function () {
tableWidget = $("#assets-table").tableBuilder({
rowTemplate: row
});
});
and then, my script I rewrote to this:
/*!
* jQuery UI Widget-factory plugin boilerplate (for 1.8/9+)
* Author: #addyosmani
* Further changes: #peolanha
* Licensed under the MIT license
*/
String.prototype.format = function (values) {
var regex = /\{([\w-.]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g;
var getValue = function (key) {
var value = values,
arr, type;
if (values == null || typeof values === 'undefined') return null;
if (key.indexOf('.')) {
arr = key.split('.');
while (arr.length && value) {
value = value[arr.shift()];
}
} else {
value = val && val[key] || values[key];
}
type = typeof value;
return type === 'string' || type === 'number' ? value : null;
};
return this.replace(regex, function (match) {
//match will look like {sample-match}
//key will be 'sample-match';
var key = match.substr(1, match.length - 2);
var value = getValue(key);
return value != null ? value : match;
});
};
; (function ($, window, document, undefined) {
// define your widget under a namespace of your choice
// with additional parameters e.g.
// $.widget( "namespace.widgetname", (optional) - an
// existing widget prototype to inherit from, an object
// literal to become the widget's prototype );
$.widget("skipstone.tableBuilder", {
//Options to be used as defaults
options: {
json: null,
rowTemplate: null
},
//Setup widget (eg. element creation, apply theming
// , bind events etc.)
_create: function () {
// _create will automatically run the first time
// this widget is called. Put the initial widget
// setup code here, then you can access the element
// on which the widget was called via this.element.
// The options defined above can be accessed
// via this.options this.element.addStuff();
this.rows = [];
if (this.options.json != null) {
this._buildRow();
this._display();
}
},
_buildRow: function () {
var self = this;
$.each(self.options.json, function (i, item) {
self.rows.push(self.options.rowTemplate.format(item));
});
},
_display: function (el, options) {
$(this.element).html(this.rows.join());
},
add: function (row) {
this.rows.push(this.options.rowTemplate.format(row));
this._display();
},
remove: function(row) {
var match = this.options.rowTemplate.format(row);
this.rows = $.grep(this.rows, function (obj) {
return obj != match;
});
this._display();
},
// Destroy an instantiated plugin and clean up
// modifications the widget has made to the DOM
destroy: function () {
// this.element.removeStuff();
// For UI 1.8, destroy must be invoked from the
// base widget
$.Widget.prototype.destroy.call(this);
// For UI 1.9, define _destroy instead and don't
// worry about
// calling the base widget
}
});
})(jQuery, window, document);
This how now fixed my issue. You can see that I add rows by calling
tableWidget.tableBuilder("add", asset[0]);
and remove items by calling:
tableWidget.tableBuilder("remove", asset[0]);
I really hope that helps someone else :D
Cheers,
r3plica