I want to perform operation after successfully loaded the partial page using $http service in angular.
The operation is to check the checkbox based on the scope value.
Please anyone help me.
Source Code here:
This is actual code.
$http({
url : './resources/staticPages/custom-object-filter.html',
method : "GET"
}).success(function(data, status) {
$scope.data = data;
jQuery("objectViewPartial").html($compile($scope.data)($scope));
//console.log($scope.selected);
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
$("#ShowActivatedObjects").attr('checked','true');
} else {
$("#ShowActivatedObjects").attr('checked','false');
}
}).error(function(data, status) {
console.log("some error occured partial page");
});
After getting success the below code is not working.
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
$("#ShowActivatedObjects").attr('checked','true');
} else {
$("#ShowActivatedObjects").attr('checked','false');
}
I placed this code inside of success function.
Please advice where i need to place this code.
A short Angularjs excursus:
In general you should minimize the use of jQuery within angularjs as far as you can, because both concepts are working against each other.
Angularjs is based on bindings between scope variables and the template expressions. So for a check box you can easy handle it without jQuery as follows:
A good explanation you can find here:
https://stackoverflow.com/a/14520103/4852206
What you are seeing here is, that you i.ex. can bind the checkbox inputs to an array. If you are changing the array content, the checkboxes are listening and do change there state (checked or unchecked) just on the fly without the need of checking if it is active and without the Jquery dom manipulation needs.
I wrote the above excursus, because you will run into huge unstructured and unreadable code if you will use your way as best practice. Also I am afraid, that you are using your code within your controller function. In Angularjs the controller is NOT for dom manipulation, you should only use it for setting / changing the scope variables or just a minimal business logic (in general avoid it here!) Business logic should go into services and dom manipulation should go into directive's.
Your question:
Please provide more information for your problem / logic. Please provide sample checkboxes, and sample information that you pull from the server via $http
Please provide also what you want to achieve exactly. Then we can help you with a clear answer.
Sorry I know this is no answer, but I cannot add comments actually cause I am quite new here.
EDIT
Ok thanks to your comment to #Likeee I think I got you. You have a set of checkboxes on your page. On page load you want to check which checkbox needs to be active. The logic which checkbox is active on pageload comes from the server side, is that correct? If yes I would handle it as follows with angular structure (I created a fiddle for a better understanding: https://jsfiddle.net/pfk9u3h0/4/)
First you need your HTML:
<body ng-app="myApp" ng-controller="AppController">
<label ng-repeat="filter in productFilter">
<!--
simply checking if the array contains the entry. if not, it is not selected. On a click event, the selected item is added to the array.
-->
<input
type="checkbox"
value="{{filter}}"
ng-checked="selection.indexOf(filter) > -1"
ng-click="toggleSelection(filter)"/> {{filter}}
</label>
"selection Array within SelectionService": <br />{{selection}} <br /><br />
as you see, the array is also updating when you check or uncheck a box
</body>
This HTML snipped does several things:
First it declares which controller (here "AppController" to use.
Second it creates a label with a checkbox inside. The label has the Angularjs directive ngRepeat and causes to show as much checkboxes as the array "productFilter" within AppController contains.
The Javascript contains a controller which is talking to your HTML and to a service. The service is also declared below.
Controller:
app.controller("AppController", function( $scope, SelectionService ) {
// available filter checkboxes
$scope.productFilter = ['Red pants', 'Green Pants', 'Yellow pants', 'Blue pants'];
// selected filter
// normally you just would have this: $scope.selection = ['Red pants', 'Blue pants'];
// But now you want to pull the selection elsewhere:
// The selection should go in a service, because it can happen, that other pageviews of your page shall
// change the values or even get the selection as well.
$scope.selection = SelectionService.selection; // Bind the selection to the service
// With the proper binding, you can change the selection within other modules / pages etc. and this
// checkbox fields will update based on the service data!
//I just initialize the service selection array, butbut you really want to pull it from server like this:
// SelectionService.loadSelection()
// .then(function(data) {
// if you like, yo can do sth. with this promise callback
// });
// Change selection for the clicked input field
$scope.toggleSelection = function toggleSelection(filter) {
var idx = $scope.selection.indexOf(filter);
// is currently selected
if (idx > -1) {
SelectionService.removeSelection(idx);
}
// is newly selected
else {
SelectionService.addSelection(filter);
}
};
});
With $scope.productFilter = ['Red pants', 'Green Pants', 'Yellow pants', 'Blue pants']; we declare an array over which we iterate within our HTML. This contains all checkboxes with names.
Now we need an array with all selected checkboxes. This is saved in $scope.selection and is bound to the service which i show at the end:
$scope.selection = SelectionService.selection;
The rest within the controller is just to set the new values, if you uncheck or check a checkbox
The Service:
app.service("SelectionService", function ($http, $q) {
var obj = {
selection : ['Red pants', 'Blue pants'],
loadSelection : function(){
var that = this;
var deferred = $q.defer();
var config = {
responseType : "json",
cache: true
}
$http.get("/getSelectionFromServer", null, config)
.success(function(response){
if(response) {
// IMPORTANT: Use a deep copy, otherwise you will loose the binding
angular.copy(response,that.selection);
}
deferred.resolve(response);
})
.error(function(){
deferred.reject();
});
return deferred.promise;
},
addSelection : function (filter) {
this.selection.push(filter)
},
removeSelection : function (index) {
this.selection.splice(index, 1);
};
return obj;
});
The Service is doing 4 things: It holds and initilizes (if you like) an array "selection". And it offers a method to load new data from server and save it to the array. Here it is important that you use the copy method, otherwise you will loose any binding. And it offers methods to set and remove selections.
in my example i dont use the load method, because I do not have any serverside scripts here...I just take the initialization values. But you should use my load method.
Hi your comparison is probably bad.
if(angular.equals($scope.selected, "ShowActivatedObjects")) {
I don't know what value is in $scope.selected, but i think this value is a checkbox state which is boolean type. So you are comparing boolean with string.
Check out https://docs.angularjs.org/api/ng/function/angular.equals
Finally i achieved my requirement:
// $scope.getCustomObjectsBasedOnObjectTypeSelection = getCustomObjectsBasedOnObjectTypeSelection;
$scope.selected = ['Show All'];
var IsActive = "";
$scope.objectSelection = function objectSelection(objectType) {
var idx = $scope.selected.indexOf(objectType);
// is currently selected
if (idx > -1) {
$scope.selected.splice(idx, 1);
}
// is newly selected
else {
$scope.selected.push(objectType);
}
};
Related
I am using Select2 as multiselect on an input field. I am trying to find out which choice is being clicked, but if I look at the event thrown when clicking a choice ("choice-selected") all I can find is event.target.value which is the data array with all selected options (choices). Basically I want to access the clicked value so I can let the user edit a custom attribute in that specific choice. How could I go about doing that?
JSFiddle of the problem: http://jsfiddle.net/JtTTF/
$(test).bind("choice-selected", function(e) {
alert(e.target.value);
// this is where i would like to access the clicked choice id so i can edit the custom data attribute "reason"
});
I ran into this as well. After examining the select2.js source code, I found that the choice-selected event passes a second parameter: the dom element you clicked.
This dom element contains a data object with key "select2-data".
Your code might look something like this:
$(test).on("choice-selected", function(e, choice) {
var data = $(choice).data('select2-data');
alert(data.reason ? 'Reason: ' + data.reason : 'No Reason');
});
I've updated your jsfiddle accordingly.
Try
var selected = {};
$(test).change(function () {
var selections = $(this).select2('data');
$.each(selections, function (idx, obj) {
if (!selected[obj.id]) {
selected[obj.id] = obj;
$('#selectedText').text(JSON.stringify(obj));
return false;
}
})
});
Demo: Fiddle
Note: Haven't seen whether there is inbuilt support to do this, but a custom implementation
Is it possible to revert a value change to a view model with something other than a custom binding handler (maybe an extender) without the subscriptions firing?
For example, say you have a numeric field that only allows values up to 100. If someone types 101, we want the value to drop back to the previous value and most importantly not fire any subscriptions on the reverted value.
I'm trying to find a generic way of accomplishing this without having to write a custom binding handler that inherently would require duplication of core knockout code to handle text fields, select fields, etc.
Yes, it can be done with an extender, like this:
ko.extenders.numeric = function(target, properties) {
var result = ko.computed({
read: target,
write: function(newValue) {
var current = target();
var valueToWrite = newValue;
if(properties) {
if(properties.maxNum && properties.maxNum < newValue) {
valueToWrite = current;
}
if(properties.minNum && properties.minNum > newValue) {
valueToWrite = current;
}
}
if(valueToWrite !== current) {
target(valueToWrite);
} else {
target.notifySubscribers(valueToWrite);
}
}
});
result(target());
return result;
};
And this is how you use it:
self.number = ko.observable().extend({numeric: { minNum: 50, maxNum: 100} });
You can test that in the fiddle I've created.
You can comment the target.notifySubscribers(valueToWrite) line but what will happen is that if you change that value from outside (like in an input element), the value will not be updated back to the previous one.
I went down the same route that #Jalayn had suggested already, and ended up doing something similar to the issue listed in the comments on his answer. I'm still not a huge fan of this as it requires you to check at the top of the subscription to see if the value has actually changed, but at least it is possible.
The full solution and QUnit tests are posted here: https://github.com/gotdibbs/ko.extenders.filteredUpdate/.
The key components to making this work are an extender to "protect" the view model from unwanted changes using a computed observable, and a custom function extending subscribables to work in place off a normal subscription which would fire on every change regardless of if the value is actually changing.
I am creating a list of links using a knockout binding:
The Javascript and view model looks like this:
$(function () {
var adminViewModel = function()
{
var self = this;
self.leftItems = ko.observable([ { Name: "Item1", Id: 0 }]);
self.getChildren = function (id, list) {
var url ="#Url.Content("~/api/Test/GetChildren/")" + id;
$.getJSON(url, function (data) {
list(data);
});
};
}
var Admin3App = window.Admin3App = window.Admin3App || {};
Admin3App.viewModel = new adminViewModel();
ko.applyBindings(Admin3App.viewModel);
function getLeftChildren(id)
{
Admin3App.viewModel.getChildren(id, Admin3App.viewModel.leftItems);
}
getLeftChildren(0);
}
(EDIT: the init of the ko.observable was missing an Id (even though this did not cause an error, added it)
How this works is the view model will load a bunch of items that has no parent (id 0).
For the sake of simplicity I only included the left version. But the page has a list of items on the left and right so there is a function for each.
The left items is populated to view model left items and displayed as below. But each item is a link and once clicked via javascript will refresh the items based on the parent Id. (Much like browsing a folder viewer in explorer).
But i cannot figure out how to declare the binding in knockout to build up the url. I know this is probably rediculously easy and I am just missing it.
here is the html I have attempted amongst others
<div class="leftView">
<div data-bind="foreach: leftItems">
<a data-bind="text: Name, attr : { href:'javascript:getLeftChildren(' + Id + ')' }"></a><br />
</div>
</div>
(EDIT: missed a single quote, but got the same error anyway)
But I keep getting a binding error
A couple of things:
1) leftItems should ideally be an observableArray
2) The initial object you push into leftItems does not have an Id property
Here is a JSfiddle that corrects a few things and demonstrates loading an initial set of data and then calling again to load different data. I've also moved the outside JavaScript call inside the viewModel and switched to using a click handler to pass refresh the data (note that a click binding will automatically send the data item into the event handler as the first parameter, so you have access to the id you need for creating your URL).
http://jsfiddle.net/jearles/xTHFg/
I am creating a simple list of folders which when clicked will be marked selected that is their model will have its selected property changed to true. I want this selection to be exclusive - all other folders should be marked unselected before a folder is marked selected.
Right all I can think of looping through the collection to change the property of every model. Is there some easier way I can accomplish this?
In a project I'm working on we maintain the 'selected' item at the collection level. We have added getCurrent() and setCurrent(model) methods and the collection will raise a change:selection event. This has worked real well for us.
--EDIT: code sample per request.--
The following is part of our base.collection.js which we extend for all our collections. You'll notice that we can set current based on id or the actual model so col.setCurrent(123) and col.setCurrent(anInstanceOfAModel) both work. Also calling setCurrent for a model that is already the current doesn't fire the change event
setCurrent: function (id)
{
var isModel = !(_.isString(id) || _.isNumber(id));
var triggerChange = this._setupCurrent(isModel
? id
: this.get(id));
if (triggerChange)
this.trigger("change:current", this._current);
return this.getCurrent();
},
getCurrent: function ()
{
return this._current;
},
_setupCurrent: function (current)
{
var old = this._current;
this._current = current;
if (current && old && old.id == current.id)
return false;
return true;
},
You can also have a "state" object, in which you store the last selected folder.
Then you change that one back to not selected, without having to loop through the entire collection.
Firstly, is it possible? Been struggling with this one for hours; I think the reason my events aren't firing is because one event is unbinding/overwriting the other. I want to bind two change events to the same element. How can I do that?
As per request, here's the function I'm struggling with:
(function($) {
$.fn.cascade = function(name, trigger, url) {
var cache = {};
var queue = {};
this.each(function() {
var $input = $(this);
var $trigger = $input.closest('tr').prev('tr').find(trigger);
//$input.hide();
var addOptions = function($select, options) {
$select.append('<option value="">- Select -</option>');
for(var i in options) {
$select.append('<option value="{0}">{1}</option>'.format(options[i][0], options[i][1]));
}
$select.val($input.val()).trigger('change');
}
var $select = $('<select>')
// copy classes
.attr('class', $input.attr('class'))
// update hidden input
.bind('change', function() {
$input.val($(this).val());
})
// save data for chaining
.data('name', name)
.data('trigger', $trigger);
$input.after($select);
$trigger.bind('change', function() {
var value = $(this).val();
$select.empty();
if(value == '' || value == null) {
$select.trigger('change');
return;
}
// TODO: cache should be a jagged multi-dimensional array for nested triggers
if(value in cache) {
addOptions($select, cache[value]);
} else if(value in queue) {
$select.addClass('loading');
queue[value].push($select);
} else {
var getDict = {}
getDict[name] = value;
// TODO: use recursion to chain up more than one level of triggers
if($(this).data('trigger')) {
getDict[$(this).data('name')] = $(this).data('trigger').val();
}
$select.addClass('loading');
queue[value] = [$select];
$.getJSON(url, getDict, function(options) {
cache[value] = options;
while(queue[value].length > 0) {
var $select = queue[value].pop();
$select.removeClass('loading');
addOptions($select, options);
}
});
}
}).trigger('change');
});
return this;
}
})(jQuery);
The relevant chunk of HTML is even longer... but essentially it's a select box with a bunch of years, and then an <input> that gets (visibly) replaced with a <select> showing the vehicle makes for that year, and then another <input> that gets replaced with the models for that make/year.
Actually, it seems to be running pretty well now except for on page load. The initial values are getting wiped.
Solved the issue by pulling out that $select.bind() bit and making it live:
$('select.province').live('change', function() {
$(this).siblings('input.province').val($(this).val());
});
$('select.make').live('change', function() {
$(this).siblings('input.make').val($(this).val());
});
$('select.model').live('change', function() {
$(this).siblings('input.model').val($(this).val());
});
Sucks that it's hard-coded in there for my individual cases though. Ideally, I'd like to encapsulate all the logic in that function. So that I can just have
$('input.province').cascade('country', 'select.country', '/get-provinces.json');
$('input.make').cascade('year', 'select.year', '/get-makes.json');
$('input.model').cascade('make', 'select.make', '/get-models.json');
Yes that is possible.
$(…).change(function () { /* fn1 */ })
.change(function () { /* fn2 */ });
jQuery event binding is additive, calling .change a second time does not remove the original event handler.
Ryan is correct in jQuery being additive, although if you find there are problems because you are chaining the same event, beautiful jQuery allows another approach, and that is calling the second function within the first after completion of the first as shown below.
$('input:checkbox').change(function() {
// Do thing #1.; <-- don't forget your semi-colon here
(function() {
// Do thing #2.
});
});
I use this technique frequently with form validation, one function for checking and replacing disallowed characters input, and the second for running a regex on the results of the parent function.
Update to Post:
OK... You all are quick to beat on me with your negative scores, without understanding the difference in how we each view Mark's request. I will proceed to explain by example why my approach is the better one, as it allows for the greatest flexibility and control. I have thrown up a quick example at the link below. A picture's worth a 1000 words.
Nested Functions on One Event Trigger
This example shows how you can tie in three functions to just one change event, and also how the second and third functions can be controlled independently, even though they are still triggered by the parent change event. This also shows how programmatically the second and third functions can BOTH be tied into the same parent function trigger, yet respond either with or independently (see this by UNCHECKING the checkbox) of the parent function it is nested within.
$('#thecheckbox').change(function() {
$("#doOne").fadeIn();
if ($('#thecheckbox').attr('checked')) { doFunc2() }
else { doFunc3() };
function doFunc2() { $("#doTwo").fadeIn(); return true; }
function doFunc3() { $("#doTwo").fadeOut(); return true; }
$("#doThree").fadeIn();
});
I've included the third 'Do thing #3 in the example, to show how yet another event can follow the two nested functions as described earlier.
Forgive the earlier bad pseudocode originally posted first, as I always use ID's with my jQuery because of their ability to give everything an individual status to address with jQuery. I never use the 'input:checkbox' method in my own coding, as this relies on the 'type' attribute of an input statement, and therefore would require extra processing to isolate any desired checkbox if there is more than one checkbox in the document. Hopefully, the example will succeed at articulating what my comments here have not.
I am actually not sure exactly if you can bind two different change events. But, why not use logic to complete both events? For example...
$('input:checkbox').change(function() {
// Do thing #1.
// Do thing #2.
});
That way, you get the same benefit. Now, if there are two different things you need to do, you may need to use logic so that only one or the other thing happens, but I think you would have to do that anyway, even if you can bind two change events to the same element.