Passing this to jQuery plugin - javascript

I am using the autoSuggest plugin from http://code.drewwilson.com/entry/autosuggest-jquery-plugin across my website to search for various different items.
One major function with this plugin is that when a selection is added, a callback function gets called and I check whether this is new data added or it is fetched from the web service and then update some variables(which I use for submission)
Up-till now, I have been using it without any thought but now I have decide to go OOP way and create a JS object to wrap this plugin and other associated functions(common callbacks) and config values(urls) etc.
In the class I have different data variables which will be updated as the selection are added and removed. halfway through the code, I realized that the callbacks take only one argument and there is no way(apparent to me) by which I could pass those variables or the object's context to these callbacks
Below is the code I have written so far :
var coSuggest = function(options) {
this.selector = options.selector;
this.options = options;
//this.that = this;
//this.submitURL = options.submitURL;
//if y=type is not defined , default type will be people
if ( typeof (options.type) === 'undefined') {
this.type = 'people';
} else {
this.type = options.type;
}
// if as_options are not defined, as_options will be a empty object
//console.log(typeof(options.as_options));
if ( typeof (options.as_options) === 'undefined') {
this.as_options = {};
} else {
this.as_options = options.as_options;
}
this.valuesField = $('#as-values-' + this.as_options.asHtmlID);
//the initData field will be an array of object containing {id,value} of prefilled items initially
//the addData field will be an array of object containing {id,value} of new selected items from the suggestions
//the newData field will be an array of strings [values] containing the new values added
//the removeData field will be an array of objects containing {id,value} of the items from data to be removed
if ( typeof (options.initData) !== 'undefined') {
this.initData = options.initData;
this.as_options.preFill = this.initData;
}
this.as_options.selectionAdded = this.selectionAdded;
// callback function to be added in as_options as selectionAdded
this.as_options.selectionRemoved = function(elem) {
console.log(elem);
}
//return this
};
//urlConf for searching the items sitewide
coSuggest.prototype.urlConf = {
people : 'abc',
interest : "index.php/profile/getInterestsSkillsTags",
skill : 'xyz',
teacher : 'xyz',
designation : 'xyz',
city : 'xyz',
subject : 'xyz',
};
// callback function to be added in as_options as selectionAdded
coSuggest.prototype.selectionAdded = function(elem) {
//console.log($(this).find('.as-values'));
console.log(elem);
}
//bind function to bind the autoSuggest plugin with the selector provided
coSuggest.prototype.bind = function(options) {
//console.log(as_options);
$(this.selector).autoSuggest(base_url + this.urlConf[this.type], this.as_options);
}
How can I do this without losing the re-usability of the code ?

I am answering this question as I think I have solved the problem by extending the code. I added a new parameter in the selectionAdded callback and supplied the function reference in that function while calling.

Related

AngularJS : check if a model value has changed

IS there a way to check a dirty flag on the model itself, independent of the view?
I need the angular controller to know what properties have been changed, in order to only save changed variables to server.
I have implemented logic regarding if my entire form is dirty or pristine, but that is not specific enough
I could just slap a name and ng-form attribute on every input, to make it recognizable as a form in the controller, but then I end up with a controller that is strongly coupled with the view.
Another not-so appealing approach is to store the initial values that every input is bound to in a separate object, then compare the current values with the initial values to know if they have changed.
I checked Monitor specific fields for pristine/dirty form state and AngularJS : $pristine for ng-check checked inputs
One option I could think of is
As you get a model/object from service, create a replica of the model within the model and bind this new model to your view.
Add a watch on the new Model and as the model changes, use the replica to compare old and new models as follows
var myModel = {
property1: "Property1",
property2: "Property2",
array1:["1","2","3"]
}
var getModel = function(myModel){
var oldData = {};
for(var prop in myModel){
oldData.prop = myModel[prop];
}
myModel.oldData = oldData;
return myModel;
}
var getPropChanged = function(myModel){
var oldData = myModel.oldData;
for(var prop in myModel){
if(prop !== "oldData"){
if(myModel[prop] !== oldData[prop]){
return{
propChanged: prop,
oldValue:oldData[prop],
newValue:myModel[prop]
}
}
}
}
}
You may find it easiest to store and later compare against the JSON representation of the object, rather than looping through the various properties.
See Detect unsaved data using angularjs.
The class shown below may work well for your purpose, and is easily reused across pages.
At the time you load your models, you remember their original values:
$scope.originalValues = new OriginalValues();
// Set the model and remember it's value
$scope.someobject = ...
var key = 'type-' + $scope.someobject.some_unique_key;
$scope.originalValues.remember(key, $scope.someobject);
Later you can determine if it needs to be saved using:
var key = 'type-' + $scope.someobject.some_unique_key;
if ($scope.originalValues.changed(key, $scope.someobject)) {
// Save someobject
...
}
The key allows you to remember the original values for multiple models. If you only have one ng-model the key can simply be 'model' or any other string.
The assumption is that properties starting with '$' or '_' should be ignored when looking for changes, and that new properties will not be added by the UI.
Here's the class definition:
function OriginalValues() {
var hashtable = [ ]; // name -> json
return {
// Remember an object returned by the API
remember: function(key, object) {
// Create a clone, without system properties.
var newobj = { };
for (var property in object) {
if (object.hasOwnProperty(property) && !property.startsWith('_') && !property.startsWith('$')) {
newobj[property] = object[property];
}
}
hashtable[key] = newobj;
},// remember
// See if this object matches the original
changed: function(key, object) {
if (!object) {
return false; // Object does not exist
}
var original = hashtable[key];
if (!original) {
return true; // New object
}
// Compare against the original
for (var property in original) {
var changed = false;
if (object[property] !== original[property]) {
return true; // Property has changed
}
}
return false;
}// changed
}; // returned object
} // OriginalValues

Overriding a breeze entity value getter setter seems to break change tracking

I am using breeze to communicate with Web.API 2.1
In my backend I save some values as a list of strings (instead of saving one-to-many relations). In the front end I want to break these values, edit them, put them back together and persist them to the DB.
emailsString is the actual property that is persisted to the DB and exists in the model.
fullName acts as an "interface" to reading and modifying the first and last name properties.
I have the following:
function registerUserProfile(metadataStore) {
metadataStore.registerEntityTypeCtor('UserProfile', profile, profileInitializer);
function profile() {
this.fullName = '';
this.emails = [];
}
function profileInitializer(newItem) {
if (!newItem.emailsString || newItem.emailsString.length === 0) newItem.emails = [{ email: '' }];
}
Object.defineProperty(profile.prototype, 'fullName', {
get: function() {
var fn = this.firstName;
var ln = this.lastName;
return ln ? fn + ' ' + ln : fn;
},
set: function (value) {
var parts = value.split(' ');
this.firstName = parts.shift();
this.lastName = parts.shift() || '';
}
});
Object.defineProperty(profile.prototype, 'emailsString', {
get: function () {
return objectToStringArray(this.emails, 'email');
},
set: function (value) {
this.emails = stringToObjArray(value, 'email');
}
});
function objectToStringArray(objectArray, objectValueKey) {
var retVal = '';
angular.forEach(objectArray, function (obj) {
retVal += obj[objectValueKey] + ';';
});
if (retVal.length > 0)
retVal = retVal.substring(0, retVal.length - 1); //remove last ;
return retVal;
}
function stringToObjArray(stringArray, objectValueKey) {
var objArray = [];
angular.forEach(stringArray.split(';'), function (str) {
var item = {};
item[objectValueKey] = str;
objArray.push(item);
});
return objArray;
}
If I modify the emailString value and call saveChanges on breeze nothing happens. If I modify the fullName property ALL changes are detected and saveChanges sends the correct JSON object for saving (including emailString value).
From what I understand, overriding the emailString property I somehow break the change tracking for this property. fullName is not a mapped property and thus is not overriding anything so it works. Am I going the correct way? If so is there a way to notify breeze that the overriden property has changed?
In general, Breeze takes over each property on an object and insures that internally it is informed about any changes to each property. How this is done is different depending on whether you are using Angular, Knockout or Backbone ( or a custom modelLibrary adapter).
But if you plan on modifying the property yourself to do something similar you need to insure that breeze is still getting notified.
Based on your posted code I'm assuming that you are using Angular. In that case you first need to determine whether your code is getting executed before or after Breeze's code.
My guess is that if you make your changes early enough then Breeze will be able to wrap them successfully. However, if your changes occur after Breeze's then you need to insure that Breeze's code is invoked as well. Debugging into the source for this is probably your best bet. And the Breeze Angular adapter is a good source as a example of how to wrap a property that might already be wrapped with another defineProperty.

How can I make Ember.js handlebars #each iterate over objects?

I'm trying to make the {{#each}} helper to iterate over an object, like in vanilla handlebars. Unfortunately if I use #each on an object, Ember.js version gives me this error:
Assertion failed: The value that #each loops over must be an Array. You passed [object Object]
I wrote this helper in attempt to remedy this:
Ember.Handlebars.helper('every', function (context, options) {
var oArray = [];
for (var k in context) {
oArray.push({
key : k,
value : context[k]
})
}
return Ember.Handlebars.helpers.each(oArray, options);
});
Now, when I attempt to use {{#every}}, I get the following error:
Assertion failed: registerBoundHelper-generated helpers do not support use with Handlebars blocks.
This seems like a basic feature, and I know I'm probably missing something obvious. Can anyone help?
Edit:
Here's a fiddle: http://jsfiddle.net/CbV8X/
Use {{each-in}} helper. You can use it like like {{each}} helper.
Example:
{{#each-in modelWhichIsObject as |key value|}}
`{{key}}`:`{{value}}`
{{/each-in}}
JS Bin demo.
After fiddling with it for a few hours, I came up with this hacky way:
Ember.Handlebars.registerHelper('every', function(context, options) {
var oArray = [], actualData = this.get(context);
for (var k in actualData) {
oArray.push({
key: k,
value: actualData[k]
})
}
this.set(context, oArray);
return Ember.Handlebars.helpers.each.apply(this,
Array.prototype.slice.call(arguments));
});
I don't know what repercussions this.set has, but this seems to work!
Here's a fiddle: http://jsfiddle.net/CbV8X/1/
I've been after similar functionality, and since we're sharing our hacky ways, here's my fiddle for the impatient: http://jsfiddle.net/L6axcob8/1/
This fiddle is based on the one provided by #lxe, with updates by #Kingpin2k, and then myself.
Ember: 1.9.1, Handlebars: 2.0.0, jQuery 2.1.3
Here we are adding a helper called every which can iterate over objects and arrays.
For example this model:
model: function() {
return {
properties: {
foo: 'bar',
zoo: 'zar'
}
};
}
can be iterated with the following handlebars template:
<ul class="properties">
{{#every p in properties}}
<li>{{p.key}} : {{p.value}}</li>
{{/every}}
</ul>
every helper works by creating an array from the objects keys, and then coordinating changes to Ember by way of an ArrayController. Yeah, hacky. This does however, let us add/remove properties to/from an object provided that object supports observation of the [] property.
In my use case I have an Ember.Object derived class which notifies [] when properties are added/removed. I'd recommend looking at Ember.Set for this functionality, although I see that Set been recently deprecated. As this is slightly out of this questions scope I'll leave it as an exercise for the reader. Here's a tip: setUnknownProperty
To be notified of property changes we wrap non-object values in what I've called a DataValueObserver which sets up (currently one way) bindings. These bindings provide a bridge between the values held by our internal ArrayController and the object we are observing.
When dealing with objects; we wrap those in ObjectProxy's so that we can introduce a 'key' member without the need to modify the object itself. Why yes, this does imply that you could use #every recursively. Another exercise for the reader ;-)
I'd recommend having your model be based around Ember.Object to be consistent with the rest of Ember, allowing you to manipulate your model via its get & set handlers. Alternatively, as demonstrated in the fiddle, you can use Em.Get/Em.set to access models, as long as you are consistent in doing so. If you touch your model directly (no get/set), then every won't be notified of your change.
Em.set(model.properties, 'foo', 'asdfsdf');
For completeness here's my every helper:
var DataValueObserver = Ember.Object.extend({
init: function() {
this._super();
// one way binding (for now)
Em.addObserver(this.parent, this.key, this, 'valueChanged');
},
value: function() {
return Em.get(this.parent, this.key);
}.property(),
valueChanged: function() {
this.notifyPropertyChange('value');
}
});
Handlebars.registerHelper("every", function() {
var args = [].slice.call(arguments);
var options = args.pop();
var context = (options.contexts && options.contexts[0]) || this;
Ember.assert("Must be in the form #every foo in bar ", 3 == args.length && args[1] === "in");
options.hash.keyword = args[0];
var property = args[2];
// if we're dealing with an array we can just forward onto the collection helper directly
var p = this.get(property);
if (Ember.Array.detect(p)) {
options.hash.dataSource = p;
return Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
}
// create an array that we will manage with content
var array = Em.ArrayController.create();
options.hash.dataSource = array;
Ember.Handlebars.helpers.collection.call(this, Ember.Handlebars.EachView, options);
//
var update_array = function(result) {
if (!result) {
array.clear();
return;
}
// check for proxy object
var result = (result.isProxy && result.content) ? result.content : result;
var items = result;
var keys = Ember.keys(items).sort();
// iterate through sorted array, inserting & removing any mismatches
var i = 0;
for ( ; i < keys.length; ++i) {
var key = keys[i];
var value = items[key];
while (true) {
var old_obj = array.objectAt(i);
if (old_obj) {
Ember.assert("Assume that all objects in our array have a key", undefined !== old_obj.key);
var c = key.localeCompare(old_obj.key);
if (0 === c) break; // already exists
if (c < 0) {
array.removeAt(i); // remove as no longer exists
continue;
}
}
// insert
if (typeof value === 'object') {
// wrap object so we can give it a key
value = Ember.ObjectProxy.create({
content: value,
isProxy: true,
key: key
});
array.insertAt(i, value);
} else {
// wrap raw value so we can give it a key and observe when it changes
value = DataValueObserver.create({
parent: result,
key: key,
});
array.insertAt(i, value);
}
break;
}
}
// remove any trailing items
while (array.objectAt(i)) array.removeAt(i);
};
var should_display = function() {
return true;
};
// use bind helper to call update_array if the contents of property changes
var child_properties = ["[]"];
var preserve_context = true;
return Ember.Handlebars.bind.call(context, property, options, preserve_context, should_display, update_array, child_properties);
});
Inspired by:
How can I make Ember.js handlebars #each iterate over objects?
http://mozmonkey.com/2014/03/ember-getting-the-index-in-each-loops/
https://github.com/emberjs/ember.js/issues/4365
https://gist.github.com/strathmeyer/1371586
Here's that fiddle again if you missed it:
http://jsfiddle.net/L6axcob8/1/

jQuery style getter and setters

I have a constructor Dropdown which will take an array as a parameter. This parameter will be used by a method attached to the prototype. The parameter is an array which should be turned into a parameter of jQuery option objects, which should be the drop down menu options for the select element. Currently I have:
function Dropdown(data) {
this.sel = $('<select>');
}
Dropdown.prototype.options = function (options) {
var self = this; //using $(this) doesn't work either
if ( !options ) {
//if null return the current values of
return self.sel.html();
} else {
//make a jQuery option object out of every item in the array
//set the sel property's html to that
self.sel.html = ($.map(options, function (val) {
return $('<option>').text(val).val(val);
}));
}
}
var testArray = ['one', 'two', 'three'];
var dropdown = new Dropdown(testArray);
dropdown.sel.appendTo($('body'));
console.log(dropdown.options()); //nothing outputted to the console
by passing testArray into the Dropdown constructor this should set the html property of sel, but it doesn't and trying to use the jQuery style getter prints nothing to the console. dropdown is appended to the page, just with no options.
To start with, you're not calling the options function in the prototype. After calling it, some other bugs showed up.
self.sel.html = ($.map(options, function (val) {
return $('<option>').text(val).val(val);
}));
This turns self.sel.html into an array filled with jQuery option elements, which you're doing nothing with.
I changed it a little bit to get it working. See if it works for you. I believe it's easy to understand.
function Dropdown(data) {
this.sel = $('<select>');
this.options(data);
}
Dropdown.prototype.options = function (options) {
var sel = this.sel;
options && options.forEach(function ( val ) {
$('<option>').val(val).text(val).appendTo(sel);
});
}
Fiddle: http://jsfiddle.net/b7pvS/
Right now your constructor does nothing other than to create a select element in jQuery. You pass in the data parameter and do nothing with it. You simply need to add this.options(data); in the constructor and you should be good to go.
function Dropdown(data) {
this.sel = $('<select>');
this.options(data);
}

Creating methods on the fly

Hi I'm trying to author a jQuery plugin and I need to have methods accessible to elements after they are initialized as that kind of object, e.g.:
$('.list').list({some options}); //This initializes .list as a list
//now I want it to have certain methods like:
$('.list').find('List item'); //does some logic that I need
I tried with
$.fn.list = function (options) {
return this.each(function() {
// some code here
this.find = function(test) {
//function logic
}
}
}
and several other different attempts, I just can't figure out how to do it.
EDIT:
I'll try to explain this better.
I'm trying to turn a table into a list, basically like a list on a computer with column headers and sortable items and everything inbetween. You initiate the table with a command like
$(this).list({
data: [{id: 1, name:'My First List Item', date:'2010/06/26'}, {id:2, name:'Second', date:'2010/05/20'}]
});
.list will make the <tbody> sortable and do a few other initial tasks, then add the following methods to the element:
.findItem(condition) will allow you to find a certain item by a condition (like findItem('name == "Second"')
.list(condition) will list all items that match a given condition
.sort(key) will sort all items by a given key
etc.
What's the best way to go about doing this?
If you want these methods to be available on any jQuery object, you will have to add each one of them to jQuery's prototype. The reason is every time you call $(".list") a fresh new object is created, and any methods you attached to a previous such object will get lost.
Assign each method to jQuery's prototype as:
jQuery.fn.extend({
list: function() { .. },
findItem: function() { .. },
sort: function() { .. }
});
The list method here is special as it can be invoked on two occasions. First, when initializing the list, and second when finding particular items by a condition. You would have to differentiate between these two cases somehow - either by argument type, or some other parameter.
You can also use the data API to throw an exception if these methods are called for an object that has not been initialized with the list plugin. When ('xyz').list({ .. }) is first called, store some state variable in the data cache for that object. When any of the other methods - "list", "findItem", or "sort" are later invoked, check if the object contains that state variable in its data cache.
A better approach would be to namespace your plugin so that list() will return the extended object. The three extended methods can be called on its return value. The interface would be like:
$('selector').list({ ... });
$('selector').list().findOne(..);
$('selector').list().findAll(..);
$('selector').list().sort();
Or save a reference to the returned object the first time, and call methods on it directly.
var myList = $('selector').list({ ... });
myList.findOne(..);
myList.findAll(..);
myList.sort();
I found this solution here:
http://www.virgentech.com/blog/2009/10/building-object-oriented-jquery-plugin.html
This seems to do exactly what I need.
(function($) {
var TaskList = function(element, options)
{
var $elem = $(element);
var options = $.extend({
tasks: [],
folders: []
}, options || {});
this.changed = false;
this.selected = {};
$elem.sortable({
revert: true,
opacity: 0.5
});
this.findTask = function(test, look) {
var results = [];
for (var i = 0,l = options.tasks.length; i < l; i++)
{
var t = options['tasks'][i];
if (eval(test))
{
results.push(options.tasks[i]);
}
}
return results;
}
var debug = function(msg) {
if (window.console) {
console.log(msg);
}
}
}
$.fn.taskList = function(options)
{
return this.each(function() {
var element = $(this);
if (element.data('taskList')) { return; }
var taskList = new TaskList(this, options);
element.data('taskList', taskList);
});
}
})(jQuery);
Then I have
$('.task-list-table').taskList({
tasks: eval('(<?php echo mysql_real_escape_string(json_encode($tasks)); ?>)'),
folders: eval('(<?php echo mysql_real_escape_string(json_encode($folders)); ?>)')
});
var taskList = $('.task-list-table').data('taskList');
and I can use taskList.findTask(condition);
And since the constructor has $elem I can also edit the jQuery instance for methods like list(condition) etc. This works perfectly.
this.each isn't needed. This should do:
$.fn.list = function (options) {
this.find = function(test) {
//function logic
};
return this;
};
Note that you'd be overwriting jQuery's native find method, and doing so isn't recommended.
Also, for what it's worth, I don't think this is a good idea. jQuery instances are assumed to only have methods inherited from jQuery's prototype object, and as such I feel what you want to do would not be consistent with the generally accepted jQuery-plugin behaviour -- i.e. return the this object (the jQuery instance) unchanged.

Categories

Resources