I have developed below plug-in
(function($) {
$.fn.addressSearch = function(settings) {
settings = jQuery.extend({
searchClass: "quickSearch",
checkElement: "href",
dataElement: "data",
countryListClass: "countryList",
countryCode: "11455",
errorMsg: "You can only search for address in the UK.",
houseNumberClass: "TextboxHouseNumber",
postcodeClass: "postcode",
addressLine1Class: "addSearchLine1",
addressLine2Class: "addSearchLine2",
addressLine3Class: "addSearchLine3",
addressTownCityClass: "addTownCity",
ajaxUrl: "/WebService/addressLook",
submitType: "POST",
dataType: "xml",
parameters: "",
addressProcessURL: "",
callbackFunctionSingleAddress: selectAddress, //Callback 1
callbackFunctionMultipleAddress: quickBoxSearch, //Callback 2
useExternalProcessPage: false,
validateCountry: true
}, settings);
var jQueryMatchedObj = this;
function _initialize() {
_startModal(this, jQueryMatchedObj);
return false;
}
function _startModal(objClicked, jQueryMatchedObj) {
$j(objClicked).addClass(settings.searchClass);
var countryList = "." + settings.countryListClass + "";
if (settings.validateCountry) {
if ($j(countryList) && $j(countryList).val() != settings.countryCode) {
alert(settings.errorMsg);
return false;
}
}
if (settings.parameters) {
$j.ajax({
url: settings.ajaxUrl,
type: settings.submitType,
dataType: settings.dataType,
data: settings.parameters,
success: function(res) {
var addresses = eval(res.getElementsByTagName('string')[0].firstChild.data);
if (addresses.length == 0)
alert('Your address could not be found, please enter it manually');
else if (addresses.length == 1) {
//Callback 1 and parameters set here
settings.callbackFunctionSingleAddress(
addresses[0].addressLine1,
addresses[0].addressLine2,
addresses[0].addressLine3,
addresses[0].town,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
} else if (addresses.length > 1) {
//Callback 2 and parameters set here
settings.callbackFunctionMultipleAddress(
settings.callbackFunctionSingleAddress,
addresses,
settings.useExternalProcessPage,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
}
}
});
}
return false;
}
return this.unbind('click').click(_initialize);
}
})(jQuery);
Above works fine without any problem. I call this with code below
$('#mydiv').click(function() {
$(this).addressSearch(/* ... */);
});
However now I want to extend this even further with the passing both callback functions and parameters in the settings for the plugging so the plugging will be more robust.
how do I do this, basically I want to pass
settings.callbackFunctionSingleAddress(
addresses[0].addressLine1,
addresses[0].addressLine2,
addresses[0].addressLine3,
addresses[0].town,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
AND
settings.callbackFunctionMultipleAddress(
settings.callbackFunctionSingleAddress,
addresses,
settings.useExternalProcessPage,
settings.TextboxHouseNumber,
settings.postcodeClass,
settings.addressTownCityClass,
settings.addressLine1Class,
settings.addressLine2Class,
settings.addressLine3Class
);
as parameters on the click event of a div. So it would look like,
$('#mydiv').click(function() {
$(this).addressSearch({
callbackFunctionSingleAddress: callbackFuntion(param1, param2)
});
});
Above is the idea. Is this possible? Please help
If I'm reading this right, all you need to do is wrap the callbackFunction in a function block:
$('#mydiv').click(function() {
$(this).addressSearch({
callbackFunctionSingleAddress: function() { callbackFuntion(param1, param2); }
});
});
Related
I am using select2 group option with infinite scroll and data are coming by Ajax calling per page 10. Here is some problem arises, suppose user 1 has 15 data and user 2 has 15 data, at first 10 data are coming from user 1 and in next page 10 (5 data for user1 and 5 data for user2). no problem for data getting but the problem is user 1 group showing double. How can I prevent double display to my select2 options group? Has there any way to make an option group again?
HTML CODE
<div class="container">
<form id="frm">
<h1>Solution 1</h1>
<div class="row">
<div class="col-4">
<div class="form-group">
<label for="tagInput">Get data by ajax calling</label>
<select class="form-control" id="pictures_tag_input">
</select>
<small class="form-text text-muted"><p class="text-info">Infinite Scroll</p></small>
</div>
</div>
</div>
</form>
</div>
JS CODE
$(document).ready(function() {
// solution 1
//example.com/articles?page[number]=3&page[size]=1
http: $("#pictures_tag_input").select2({
placeholder: "Search for options",
ajax: {
url: "https://jsonplaceholder.typicode.com/users/1/todos",
dataType: "json",
global: false,
cache: true,
delay: 250,
minimumInputLength: 2,
data: function(params) {
// console.log(params.page || 1);
return {
q: params.term, // search term
_page: params.page || 1,
_limit: 10 // page size
};
},
processResults: function(data, params) {
params.page = params.page || 1;
var datx = getNestedChildren(data);
// console.log(datx);
return {
results: datx,
pagination: {
more: true
}
};
} //end of process results
} // end of ajax
});
function getNestedChildren(list) {
var roots = [];
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (roots.length === 0) {
var obj = {
text: "User " + node.userId,
children: [{ id: node.id, text: node.title }]
};
roots.push(obj);
} else {
var obj = {
text: "User " + node.userId,
children: [{ id: node.id, text: node.title }]
};
var rootArray = $.map(roots, function(val, i) {
var vl = "User " + node.userId;
if (val.text === vl) return val;
else return undefined;
});
if (rootArray.length > 0) {
var obj1 = {
id: node.id,
text: node.title
};
rootArray[0].children.push(obj1);
} else {
roots.push(obj);
}
}
}
return roots;
}
});
Demo
https://codepen.io/mdshohelrana/pen/MLVZEG
Just try to use the following code
templateResult: function(data) {
if (typeof data.children != 'undefined') {
$(".select2-results__group").each(function() {
if (data.text == $(this).text()) {
return data.text = '';
}
});
}
return data.text;
}
NOTE: Need to group from server side, Other wise you have to make master details from client side.
the accepted answer didn't work for me and I don't see why should that work. A return in the $.each will not return from the templateResult() function.
Here is an approach that worked for me.
It is not necessary to build a nested list by getNestedChildren(list) on javascript side. It is much easier to build it on server side instead.
The appearance of search results in the dropdown (options and the optgroups) can be customized by using the templateResult option. I removed the duplicated optgroups and labels by this option.
check the templateResult: formatOptions, part of the code
$(document).ready(function() {
$("#pictures_tag_input").select2({
placeholder: "Search for options",
templateResult: formatOptions,
ajax: {
url: "https://jsonplaceholder.typicode.com/users/1/todos",
dataType: "json",
global: false,
cache: true,
delay: 250,
minimumInputLength: 2,
data: function(params) {
return {
q: params.term,
_page: params.page || 1,
_limit: 10
};
},
processResults: function(data, params) {
params.page = params.page || 1;
return {
results: data,
pagination: {
more: true
}
};
} //end of process results
} // end of ajax
});
function formatOptions(item, container, $el) {
// optgroups section
if (item.children && item.children.length > 0) {
// don't format the repeated optgroups!
if ($(".select2-results__group").text() === item.text) {
return;
}
if ($('[aria-label="' + item.text + '"]').length > 0) {
return;
}
// the first occasion of the given optgroup
return $el;
}
// options section
// here you can implement your own logic
// if you want to customise the output of the options
$el.addClass('something-special-result result');
return $el;
}
});
maybe the problem is a source of a data
You call user 1 .... server return a 1
You call user 2 .... server return a 1
You call user 3 .... server return a 2
You call user 4 .... server return a 2
You call user 5 .... server return a 3
You call user 6 .... server return a 3
curent_user = 1;
$(document).ready(function() {
http: $("#pictures_tag_input").select2({
placeholder: "Search for options",
ajax: {
url: "https://jsonplaceholder.typicode.com/users/1/todos",
dataType: "json",
global: false,
cache: false,
minimumInputLength: 2,
data: function(params) {
console.log("params",params || 1);
return {
q: params.term, // search term
_page: curent_user,
_limit: 10 // page size
};
},
processResults: function(data, params) {
curent_user += 2;
var datx = getNestedChildren(data);
console.log("data: ", data);
return {
results: datx,
pagination: {
more: true
}
};
} //end of process results
} // end of ajax
});
function getNestedChildren(list) {
var roots = [];
for (i = 0; i < list.length; i += 1) {
node = list[i];
if (roots.length === 0) {
var obj = {
text: "User " + node.userId,
children: [{ id: node.id, text: node.title }]
};
roots.push(obj);
} else {
var obj = {
text: "User " + node.userId,
children: [{ id: node.id, text: node.title }]
};
var rootArray = $.map(roots, function(val, i) {
var vl = "User " + node.userId;
if (val.text === vl) return val;
else return undefined;
});
if (rootArray.length > 0) {
var obj1 = {
id: node.id,
text: node.title
};
rootArray[0].children.push(obj1);
} else {
roots.push(obj);
}
}
}
return roots;
}
});
so if you skip a one step
You call user 1 .... server return a 1
You call user 3 .... server return a 2
You call user 5 .... server return a 3
I just found a better solution which does not result in a (duplicated) optgroup being rendered as an empty option:
processResults: function( json, params ){
setTimeout( function() {
var $prevOptions = false;
var $prevGroup = false;
// loop
$('.select2-results__option[role="group"]').each(function(){
// vars
var $options = $(this).children('ul');
var $group = $(this).children('strong');
// compare to previous
if( $prevGroup && $prevGroup.text() === $group.text() ) {
$prevOptions.append( $options.children() );
$(this).remove();
return;
}
// update vars
$prevOptions = $options;
$prevGroup = $group;
});
}, 1 );
return json;
}
Advanced Custom Fields uses the exact same code for their WordPress plugin in order to fix this issue, ajax-load and group posts from different post-types.
At the moment I use two php files for my AJAX requests. One file holds the functions, the other file contains a switch function that determines which funtion should be fired. This works fine but recently I looked into a project that used php classes. The most interesting part I found was the fact that within the ajax calls, the url was set directly to the class and function.
The code is as follow:
class Score extends CI_Controller {
const INDIVIDUAL_SCORE_RANGE_SIZE = 7;
function __construct()
{
parent::__construct();
$this->load->model('benchmarkscore','',TRUE);
$this->load->model('aiinst','',TRUE);
$this->load->helper('score');
$this->load->helper('info');
}
function index()
{
show_404('score', false);
}
function accountingratio() {
if($this->session->userdata('logged_in'))
{
$session_data = $this->session->userdata('logged_in');
$ratio = strtolower($this->input->post('ratio'));
$filter = strtolower($this->input->post('filter'));
if(in_array($ratio, array('roi', 'rr', 'qr', 'cr', 'sr', 'vr'))) {
$ai_inst = $this->aiinst->findById($session_data['ICIOM1ID']);
$myScore = $this->benchmarkscore->getMyScoreByAccountingRatio($ratio, $session_data['ICIOM1ID']);
$avgScore = $this->benchmarkscore->getAverageScoreOfOthersByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
$allScores = $this->benchmarkscore->getAllScoresByAccountingRatio($ratio, $session_data['ICIOM1ID'], $filter, $ai_inst);
// We should only show 20 scores so we have to filter
// some data.
$filterDescription = getFilterDescription(count($allScores), $filter, $ai_inst);
$filteredScores = filterRelatedScores($allScores, $ai_inst['ICIOM1ID'], Score::INDIVIDUAL_SCORE_RANGE_SIZE);
$deviation = getBenchmarkDeviation($myScore['myscore'], $avgScore['avgscore']);
$deviation = $deviation == -1 ? 'N/A' : $deviation.'%';
$result = createResultArray($myScore['myscore'], $avgScore['avgscore'], $filteredScores,
"Uw score: ".number_format($myScore['myscore'], 2, ',', '.')."\nBenchmark afwijking: ".$deviation,
wrapText("Benchmark score: ".number_format($avgScore['avgscore'], 2, ',', '.')."\n\n" .
"test".$filterDescription,35),
$filterDescription);
$this->output->set_content_type('application/json');
$this->output->set_output(json_encode($result));
}
}
}
}
The ajax call:
function getAccountingRatioData(type, filter, callback) {
showLoader();
setTimeout(function() {
var jqxhr = $.ajax({
type: 'POST',
url: '/score/accountingratio',
data: {
ratio: type,
filter: filter
},
dataType: 'json'
}).done(function(data) {
hideLoader();
if(data == null) {
bootbox.alert("error");
} else {
setTopChartDataProvider(data.benchmark);
setBottomChartDataProvider(data.allscores, "test");
setFilterInfo(data);
}
if(callback)
callback.call();
})
.fail(function() { hideLoader();bootbox.alert("error"); });
}, 500);
}
I was wondering how this is working because I learned that classes it self don't actually do anything, they just hold things and you should alwas create an object in order to "fire" the class..
Any thoughts?
I have a collection of data whereby the requirements are for the autocomplete to show results after 3 characters have been written. However, there is one piece of data that which is only two characters long.. ('LG').
So my question is;
Is there a way to keep :minLength:3 whilst creating an exception for when certain characters are typed, such as ('LG')?
I've been trying to hard code the result within the success parameter, as can be seen below but it's not working as intended. I'm hoping I'm on the right track though?
Here is a code snippet, and plunk of complete code thus far;
success: function (LG, resp) {
if (LG.length === 2) {
LG.push({
label: 'LG',
value: 'LG',
});
}
response(LG);
var results = [];
$.each(resp.Q0, function(k, v) {
if (v.indexOf(request.term.toUpperCase()) >= 0) {
results.push(v);
}
});
response(results);
}
});
},
https://plnkr.co/edit/wfAoi0sZDdDd0pCufQi9
I think an option is modify the minChars and modify the success function.
Here i add you the code, tell me if would suit your problem.
$(document).ready(function() {
setTimeout(function() {
$('#_Q0').autocomplete({
source: function(request, response) {
$.ajax({
url: "brands.json",
dataType: "JSON",
type: "GET",
success: function (resp) {
var results = [];
$.each(resp.Q0, function(k, v) {
if (request.term.length >= 3 && v.indexOf(request.term.toUpperCase()) >= 0) {
results.push(v);
}
if (request.term.length == 2 && request.term.toUpperCase() == v) {
results.push(v);
}
});
response(results);
}
});
},
autoFocus: true,
minLength: 2,
response: function(event, ui) {
if (!ui.content.length) {
var noResult = {
value: "",
label: "No results found"
};
ui.content.push(noResult);
}
}
});
var render = $('#_Q0').autocomplete('instance')._renderMenu;
$('#_Q0').autocomplete('instance')._renderMenu = function(ul, items) {
items.push({
label: 'AUTRE MARQUE',
value: 'AUTRE MARQUE',
last: true
});
render.call(this, ul, items);
};
}, 100);
});
The point i dont like too much is i had to hardcode the minChars into the success function, maybe we could find the way to recover the value from the property "minChars"
https://plnkr.co/edit/ygtMteIH1J5EI5KUMU3C?p=preview
I have a jquery context menu on my landing page where I have hardcode menu items. Now I want to get the menu items from server. Basically the idea is to show file names in a specified directory in the context menu list and open that file when user clicks it...
This is so far I have reached..
***UPDATE***
C# code
[HttpPost]
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
private string GetFileNamewithoutExtension(string filename)
{
var extension = Path.GetExtension(filename);
return filename.Substring(0, filename.Length - extension.Length);
}
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e) {
var options = {
callback: function(key) {
window.open("/HelpManuals/" + key);
},
items: {}
};
$.each(data, function (item, index) {
console.log("display name:" + index.Name);
console.log("File Path:" + index.Path);
options.items[item.Value] = {
name: index.Name,
key: index.Path
}
});
}
});
});
Thanks to Matt. Now, the build function gets fire on hover.. but im getting illegal invocation... and when iterating through json result, index.Name and this.Name gives correct result. But item.Name doesn't give anything..
to add items to the context menu dynamically you need to make a couple changes
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
build: function($trigger, e){
var options = {
callback: function (key) {
var manual;
if (key == "adminComp") {
manual = "AdminCompanion.pdf";
} else {
manual = "TeacherCompanion.pdf";
}
window.open("/HelpManuals/" + manual);
},
items: {}
}
//how to populate from model
#foreach(var temp in Model.FileList){
<text>
options.items[temp.Value] = {
name: temp.Name,
icon: 'open'
}
</text>
}
//should be able to do an ajax call here but I believe this will be called
//every time the context is triggered which may cause performance issues
$.ajax({
url: '#Url.Action("Action", "Controller")',
type: 'get',
cache: false,
async: true,
contentType: "application/json; charset=utf-8",
dataType: "json",
success: function (_result) {
if (_result.Success) {
$.each(_result, function(item, index){
options.items[item.Value] = {
name: item.Name,
icon: 'open'
}
});
}
});
return options;
}
});
so you use build and inside of that define options and put your callback in there. The items defined in there is empty and is populated in the build dynamically. We build our list off of what is passed through the model but I believe you can put the ajax call in the build like I have shown above. Hopefully this will get you on the right track at least.
I solved this problem the following way.
On a user-triggered right-click I return false in the build-function. This will prevent the context-menu from opening. Instead of opeing the context-menu I start an ajax-call to the server to get the contextMenu-entries.
When the ajax-call finishes successfully I create the items and save the items on the $trigger in a data-property.
After saving the menuItems in the data-property I open the context-menu manually.
When the build-function is executed again, I get the items from the data-property.
$.contextMenu({
build: function ($trigger, e)
{
// check if the menu-items have been saved in the previous call
if ($trigger.data("contextMenuItems") != null)
{
// get options from $trigger
var options = $trigger.data("contextMenuItems");
// clear $trigger.data("contextMenuItems"),
// so that menuitems are gotten next time user does a rightclick
// from the server again.
$trigger.data("contextMenuItems", null);
return options;
}
else
{
var options = {
callback: function (key)
{
alert(key);
},
items: {}
};
$.ajax({
url: "GetMenuItemsFromServer",
success: function (response, status, xhr)
{
// for each menu-item returned from the server
for (var i = 0; i < response.length; i++)
{
var ri = response[i];
// save the menu-item from the server in the options.items object
options.items[ri.id] = ri;
}
// save the options on the table-row;
$trigger.data("contextMenuItems", options);
// open the context-menu (reopen)
$trigger.contextMenu();
},
error: function (response, status, xhr)
{
if (xhr instanceof Error)
{
alert(xhr);
}
else
{
alert($($.parseHTML(response.responseText)).find("h2").text());
}
}
});
// This return false here is important
return false;
}
});
I have finally found a better solution after reading jquery context menu documentation, thoroughly..
C# CODE
public JsonResult GetHelpFiles()
{
List<Manuals> manuals = null;
var filesPath = Server.MapPath(#"\HelpManuals");
var standardPath = new DirectoryInfo(filesPath);
if (standardPath.GetFiles().Any())
{
manuals = standardPath.GetFiles().Select(x => new Manuals
{
Name = GetFileNamewithoutExtension(x.Name),
Path = x.Name
}).ToList();
}
return Json(manuals, JsonRequestBehavior.AllowGet);
}
HTML 5
<div id="dynamicMenu">
<menu id="html5menu" type="context" style="display: none"></menu>
</div>
JavaScript Code
$.post("/Home/GetHelpFiles", function (data) {
$.each(data, function (index, item) {
var e = '<command label="' + item.Name + '" id ="' + item.Path + '"></command>';
$("#html5menu").append(e);
});
$.contextMenu({
selector: '#helpIcon',
trigger: 'hover',
delay: 300,
items: $.contextMenu.fromMenu($('#html5menu'))
});
});
$("#dynamicMenu").on("click", "menu command", function () {
var link = $(this).attr('id');
window.open("/HelpManuals/" + link);
});
Here's my solution using deferred, important to know that this feature is supported for sub-menus only
$(function () {
$.contextMenu({
selector: '.SomeClass',
build: function ($trigger, e) {
var options = {
callback: function (key, options) {
// some call back
},
items: JSON.parse($trigger.attr('data-storage')) //this is initial static menu from HTML attribute you can use any static menu here
};
options.items['Reservations'] = {
name: $trigger.attr('data-reservations'),
icon: "checkmark",
items: loadItems($trigger) // this is AJAX loaded submenu
};
return options;
}
});
});
// Now this function loads submenu items in my case server responds with 'Reservations' object
var loadItems = function ($trigger) {
var dfd = jQuery.Deferred();
$.ajax({
type: "post",
url: "/ajax.php",
cache: false,
data: {
// request parameters are not importaint here use whatever you need to get data from your server
},
success: function (data) {
dfd.resolve(data.Reservations);
}
});
return dfd.promise();
};
Which interface or component do you suggest to display the state of parallel async calls? (The language is not so important for me, just the pattern, I can rewrite the same class / interface in javascript...)
I load model data from REST service, and I want to display pending label before the real content, and error messages if something went wrong... I think this is a common problem, and there must be an already written component, or best practices, or a pattern for this. Do you know something like that?
Here is a spaghetti code - Backbone.syncParallel is not an existing function yet - which has 2 main states: updateForm, updated. Before every main state the page displays the "Please wait!" label, and by error the page displays an error message. I think this kind of code is highly reusable, so I think I can create a container which automatically displays the current state, but I cannot decide what kind of interface this component should have...
var content = new Backbone.View({
appendTo: "body"
});
content.render();
var role = new Role({id: id});
var userSet = new UserSet();
Backbone.syncParallel({
models: [role, userSet],
run: function (){
role.fetch();
userSet.fetch();
},
listeners: {
request: function (){
content.$el.html("Please wait!");
},
error: function (){
content.$el.html("Sorry, we could not reach the data on the server!");
},
sync: function (){
var form = new RoleUpdateForm({
model: role,
userSet: userSet
});
form.on("submit", function (){
content.$el.html("Please wait!");
role.save({
error: function (){
content.$el.html("Sorry, we could not save your modifications, please try again!");
content.$el.append(new Backbone.UI.Button({
content: "Back to the form.",
onClick: function (){
content.$el.html(form.$el);
}
}));
},
success: function (){
content.$el.html("You data is saved successfully! Please wait until we redirect you to the page of the saved role!");
setTimeout(function (){
controller.read(role.id);
}, 2000);
}
});
}, this);
form.render();
content.$el.html(form.$el);
}
}
});
I created a custom View to solve this problem. (It is in beta version now.)
Usage: (Form is a theoretical form generator)
var content = new SyncLabelDecorator({
appendTo: "body",
});
content.load(function (){
this.$el.append("normal html without asnyc calls");
});
var User = Backbone.Model.extend({
urlRoot: "/users"
});
var UserSet = Backbone.Collection.extend({
url: "/users",
model: User
});
var Role = Backbone.RelationalModel.extend({
relations: [{
type: Backbone.HasMany,
key: 'members',
relatedModel: User
}]
});
var administrator = new Role({id :1});
var users = new UserSet();
content.load({
fetch: [role, users],
sync: function (){
var form = new Form({
title: "Update role",
model: role,
fields: {
id: {
type: "HiddenInput"
},
name: {
type: "TextInput"
},
members: {
type: "TwoListSelection",
alternatives: users
}
},
submit: function (){
content.load({
tasks: {
save: role
},
sync: function (){
this.$el.html("Role is successfully saved.");
}
});
}
});
this.$el.append(form.render().$el);
}
});
Code:
var SyncLabelDecorator = Backbone.View.extend({
options: {
pendingMessage: "Sending request. Please wait ...",
errorMessage: "An unexpected error occured, we could not process your request!",
load: null
},
supported: ["fetch", "save", "destroy"],
render: function () {
if (this.options.load)
this.load();
},
load: function (load) {
if (load)
this.options.load = load;
this._reset();
if (_.isFunction(this.options.load)) {
this.$el.html("");
this.options.load.call(this);
return;
}
_(this.options.load.tasks).each(function (models, method) {
if (_.isArray(models))
_(models).each(function (model) {
this._addTask(model, method);
}, this);
else
this._addTask(models, method);
}, this);
this._onRun();
_(this.tasks).each(function (task) {
var model = task.model;
var method = task.method;
var options = {
beforeSend: function (xhr, options) {
this._onRequest(task, xhr);
}.bind(this),
error: function (xhr, statusText, error) {
this._onError(task, xhr);
}.bind(this),
success: function (data, statusText, xhr) {
this._onSync(task, xhr);
}.bind(this)
};
if (model instanceof Backbone.Model) {
if (method == "save")
model[method](null, options);
else
model[method](options);
}
else {
if (method in model)
model[method](options);
else
model.sync(method == "fetch" ? "read" : (method == "save" ? "update" : "delete"), model, options);
}
}, this);
},
_addTask: function (model, method) {
if (!_(this.supported).contains(method))
throw new Error("Method " + method + " is not supported!");
this.tasks.push({
method: method,
model: model
});
},
_onRun: function () {
this.$el.html(this.options.pendingMessage);
if (this.options.load.request)
this.options.load.request.call(this);
},
_onRequest: function (task, xhr) {
task.abort = function () {
xhr.abort();
};
},
_onError: function (task, xhr) {
this._abort();
this.$el.html(this.options.errorMessage);
if (this.options.load.error)
this.options.load.error.call(this);
},
_onSync: function (task, xhr) {
++this.complete;
if (this.complete == this.tasks.length)
this._onEnd();
},
_onEnd: function () {
this.$el.html("");
if (this.options.load.sync)
this.options.load.sync.call(this);
},
_reset: function () {
this._abort();
this.tasks = [];
this.complete = 0;
},
_abort: function () {
_(this.tasks).each(function (task) {
if (task.abort)
task.abort();
});
}
});