Can’t seem to access any parameters except allBindings in custom binding within event registered with registerEventHandler - knockout-3.0

I have a knockout custom binding that wraps functionality to an image crop library (https://github.com/fengyuanchen/cropper .) I’m catching the cropend.cropper event to (eventually) attach the cropped output to an observable.
I’m using:
knockout 3.3
jquery 2.1.4
cropper 2.0
Here is the binding handler:
ko.bindingHandlers.cropper = {
init: function (element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var value = ko.unwrap(valueAccessor());
var a = 1;
ko.utils.registerEventHandler(element, "cropend.cropper", function (event) {
var previewOutputObservable = allBindings.get('previewOutput');
var valueAccessorFromAllBindings = allBindings.get('cropper');
var b = 1;
});
$element.cropper(value);
},
update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
var $element = $(element);
var value = ko.unwrap(valueAccessor());
var c = 1;
}
};
And here is the element I'm binding to:
<img class="img-responsive" data-bind="attr: {'src': sampleObservable}, cropper: { aspectRatio: 16/9 }, previewOutput: cropPreview "/>
When I put a breakpoint (in chrome) on var b = 1; none of the parameters in the init are defined except allBindings. I’ve seen several examples use this general pattern though (e.g. here). What am I doing wrong?

The outer variables are accessible through a closure context. Chrome tries to optimize the context by only including variables that are actually accessed in the closure code. Since element wasn't accessed in your code, it's not part of the context. This is a good feature as it means that variables that aren't used in any closure can be safely disposed.

This is either an issue with the Chrome debugging tools or a 'feature' of knockout.
Before var b = 1 I added the line var newValue = element. I put a breakpoint on that line and lo and behold the element parameter now has values.
It seems like the variables aren't initialized until they're used in the current context.

Related

Does elementHandler in jspdf fromHTML support multiple elements?

I am using jspdf to convert an HTML page to PDF using fromHTML(). The HTML page includes multiple images, which I need fromHTML() to ignore in order to generate the PDF.
I want to use the elementHandler to ignore the images. However, I can only get that to work with a single element ID. Here is the way the documentation shows:
var elementHandler = {
'#ignorePDF': function (element, renderer) {
return true;
}
};
I have tried to replace the '#ignorePDF' ID reference to a reference with a class that applies to all of the images:
'.ignorePDF'
or to include multiple ID's (one for each image):
'#ignorPDF1,#ignorePDF2'
but neither of those approaches has worked for me. Is there another way to accomplish this?
i figured out both issues. To reference multiple items to ignore, set it up like this:
var elementHandler = {};
elementHandlers["#img1"] = function...
elementHandlers["#img2"] = function...
also best to create a function that you can reuse rather than defining it over and over.
As for the inability to use a variable for the key, that was a dumb javascript error on my part. The variable name can be used like this:
var img1 = "#img1";
elementHandlers[img1] = function...
The # character must be included.
It would be useful if the method were modified to permit a class value to be entered so that a single class could be used to denote all items to be ignored.
You can ignore all objects of the same class (called for example no-print), as follows:
var noprints = document.getElementsByClassName("no-print");
var elementHandler = {};
for (var i=0; i<noprints.length; i++) {
elementHandler['#'+noprints[i].getAttribute('id')] = function (element, renderer) { return true; }
};

MVC 4 - Knockout custom binding and date format

I am using Asp.Net MVC 4 and Knockout in a form.
I have a date input that i wish to format.
I ended up with this solution :
http://jason-mitchell.com/web-development/binding-dates-using-knockout-moment-js/
My problem is when i submit the form, i don't have the date filled in the posted JSON.
I think the problem is located in the custom handler, but i can't find out :(
The update event fires only once at load.
View Code
#Html.TextBoxFor(Function(model) model.EndDate, New With {.data_bind = "date: EndDate"})
Custom binding Code
ko.bindingHandlers.date = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var value = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var allBindings = allBindingsAccessor();
// Date formats: http://momentjs.com/docs/#/displaying/format/
var pattern = allBindings.format || 'L';
var output = "";
if (valueUnwrapped !== null && valueUnwrapped !== undefined && valueUnwrapped.length > 0) {
output = moment(valueUnwrapped).format(pattern);
}
if ($(element).is("input") === true) {
$(element).val(output);
} else {
$(element).text(output);
}
}
};
ko binding Code
var viewModel = ko.mapping.fromJS(#Html.Raw(Model.ToJson()));
ko.applyBindings(viewModel);
Note : the date format is working as expecting.
What am i doing wrong ? :(
Without seeing your ViewModel, I can only speculate. In your code, you are binding EndDate directly to your TextBox:
#Html.TextBoxFor(Function(model) model.EndDate, New With {.data_bind = "date: EndDate"})
Your EndDate observable doesn't know anything about this value. Therefore, if your EndDate observable looks something like this in your ViewModel:
this.EndDate = ko.observable();
Then it is unaware of the value that was bound directly to the TextBox by your ASP.NET MVC code. Therefore, when you apply your KO bindings, the EndDate value that was bound directly to the TextBox gets destroyed. If this is the case, you can either: (1) initialize the EndDate observable directly from the value bound from the server Model, or, (2) initialize the EndDate observable indirectly by using a custom KO binding that initializes the EndDate observable from the value that was bound directly to the TextBox.
Example 1
This example assumes the JavaScript is directly in your view. If your JavaScript is in a separate file (as is typically the case), then you would have to pass the Model data into your ViewModel using another technique such as JSON serialization on the client before binding your ViewModel.
this.EndDate = ko.observable('#Model.EndDate.ToString()');
Example 2
The sequential order of the KO binding parameters is important in this example. The new custom binding must be the first binding in order for this to work properly:
#Html.TextBoxFor(Function(model) model.EndDate, New With {.data_bind = "initInputFromView: EndDate, date: EndDate"})
And here is the code for the custom KO binding:
ko.bindingHandlers.initInputFromView = {
init: function (element, valueAccessor) {
// reads the value stored in the <input> value attribute and initializes the observable with that value
valueAccessor()(element.value);
}
};
At last, i found the solution !
The custom binding should look like that :
ko.bindingHandlers.date = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.value.init(element, valueAccessor,
allBindingsAccessor, viewModel, bindingContext);
var value = valueAccessor();
var valueUnwrapped = ko.utils.unwrapObservable(value);
var allBindings = allBindingsAccessor();
// Date formats: http://momentjs.com/docs/#/displaying/format/
var pattern = allBindings.format || 'L';
var output = valueUnwrapped;
if (valueUnwrapped !== null && valueUnwrapped !== undefined && valueUnwrapped.length > 0) {
output = moment(valueUnwrapped).format(pattern);
}
if ($(element).is("input") === true) {
$(element).val(output);
} else {
$(element).text(output);
}
},
};
It uses the default value binding plus my custom date format.
Only on init because the update is managed by a datepicker which is correctly formated.

Custom binding for cleditor fails after sorting elements through knockout sortable

First up: check this fiddle.
I have sortable array of elements created with the Knockout sortable library. When I initially apply the binding the cleditor initializes fine.
However, when sortable elements are sorted, the cleditor fails to re-initialize (I'm not sure what happens but cleditor fails). The cleditor just displays "true" instead of actual value in Firefox, and nothing in all other browsers.
I'm trying to figure out where the problem is, whether it is on the custom binding, or jQuery-UI, or the Knockout sortable library?
I am not recieving any errors in my console.
ko.bindingHandlers.cleditor = {
init: function(element, valueAccessor, allBindingsAccessor) {
var modelValue = valueAccessor(),
allBindings = allBindingsAccessor();
var $editor = jQuery(element).cleditor({
height: 50,
controls: "bold italic underline | bullets numbering | undo redo"
});
$editor[0].change(function() {
var elementValue = $editor[0].doc.body.innerHTML;
if (ko.isWriteableObservable(modelValue)) {
modelValue(elementValue);
} else {
if (allBindings['_ko_property_writers'] && allBindings['_ko_property_writers'].cleditor) {
allBindings['_ko_property_writers'].cleditor(elementValue);
}
}
});
},
update: function(element, valueAccessor) {
var value = ko.utils.unwrapObservable(valueAccessor()) || '',
$editor = jQuery(element).cleditor();
if ($editor[0].doc.body.innerHTML !== value) {
//$editor[0].doc.body.innerHTML = value;
$editor[0].doc.body.innerHTML = value;
$editor[0].focus();
}
}
};
How can I make the cleditor to work, even after the elements are sorted?
I found this resource, but I couldn't find anything wrong in code as said in that topic.
The link you provided was helpful. The CLEditor refresh method is the right way to update it after it's dragged. It just needs to be done at the correct time, using the sortable stop event.
stop: function(event, ui) {
$(ui.item).find("textarea").cleditor()[0].refresh();
}
http://jsfiddle.net/mbest/rh8c2/1/
I also worked to integrate this into your cleditor binding. In the init function:
jQuery(document).on('sortstop', function(event, ui) {
if (jQuery.contains(ui.item[0], element)) {
jQuery(element).cleditor()[0].refresh();
}
});
I also made a change in the update function to keep the <textarea> value in sync, because refresh updates the editor's value from the <textarea>:
$editor[0].updateTextArea();
http://jsfiddle.net/mbest/jw7Je/7/

How to bind the element id to an observable using an extender in knockout?

I have a set of check boxes, each bound to a custom "checked" handler:
<input type="checkbox" name="colours-red" data-bind="jqmCheckbox: colourRed" id="check-1" />
<input type="checkbox" name="colours-green" data-bind="jqmCheckbox: colourGreen" id="check-2" />
<input type="checkbox" name="colours-blue" data-bind="jqmCheckbox: colourBlue" id="check-3" />
My view model is very easy:
this.colourRed = ko.observable(false);
this.colourGreen = ko.observable(false);
this.colourBlue = ko.observable(false);
Now, i try to extend the colours as follows, to get it automatically updated.
I need other subscribers to get notified, if this is changing:
ko.extenders.elementId = function (target, option) {
target.elId = ko.observable();
function setElementId(target, option) {
target.elId(option);
}
target.subscribe(setElementId);
return target;
};
Inside the custom binding i could get the element id:
ko.bindingHandlers.jqmCheckbox = {
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
ko.bindingHandlers.checked.update(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext);
// ...set this valueAccessor extender ?
}
};
But I'm not able to get this to work, and also i dont know if this is possible, anyway.
How can i set my extender in the custom binding handler, where the element is available as parameter?
This is the jsFiddle: http://jsfiddle.net/Tk2FZ/1/
Thanks in advance
I wouldn't bother with an extender; just set the elId property inside the binding handler.
ko.bindingHandlers.jqmCheckbox = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
if (!valueAccessor().elId) {
valueAccessor().elId = ko.observable();
}
valueAccessor().elId(element.id);
return ko.bindingHandlers.checked.init.apply(this, arguments);
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
return ko.bindingHandlers.checked.update.apply(this, arguments);
}
};
The code is inside of init because that's called when the binding is initialized on a particular element.
EDIT
If you need the observable to be available immediately, you can use an extender for that (in addition to the code above). The observable will then be updated when the binding is initialized.
ko.extenders.elementId = function (target, option) {
target.elId = ko.observable();
return target;
};
SOLVED:
after some time spent again to get this to work, i found the problem.
Here is the final working fiddle: http://jsfiddle.net/z9qZc/
I put this answer to my self, just in case this two cents can help other people to spare time.
I find this extender is a generic solution, and maybe not so bad.
Let me know what you think about.
The answer is (now) clear: initialize the extender (as in most of cases it would be enough to read the documentation with care...).
self.colourRed = ko.observable(true).extend({elementId:""});
self.colourGreen = ko.observable(false).extend({elementId:""});
self.colourBlue = ko.observable(false).extend({elementId:""});
Thanks to Cyanfish that pointed out that my first try was bloated with unnecessary code, and helped my a lot.
Thanks to all knockout people who made this brilliant artwork to help us all.

Knockout, CKEditor & Single Page App

I have a situation involving KnockoutJS & CKEditor.
Basically we've got part of our site that is 'single page' app style, currently it just involves 2 pages but will likely expand over time, currently it's just a 'listings' page and a 'manage' page for the items in the list.
The manage page itself requires some sort of rich text editor, we've gone with CKEditor for a company wide solution.
Because these 2 pages are 'single page' style obviously CKEditor can't register against the manage elements because they aren't there on page load - simple enough problem to fix. So as a sample I attached CKEditor on a click event which worked great. The next problem was that then the Knockout observables that had been setup weren't getting updated because CKEditor doesn't actually modify the textarea it's attached too it creates all these div's/html elements that you actually edit.
After a bit of googleing I found an example of someone doing this with TinyMCE - http://jsfiddle.net/rniemeyer/GwkRQ/ so I thought I could adapt something similar to this for CKEditor.
Currently I'm quite close to having a working solution, I've got it initialising and updating the correct observables using this technique (I'll post code at the bottom) and even posting back to the server correctly - fantastic.
The problem I'm currently experiencing is with the 'Single Page' app part and the reinitialisation of CKEditor.
Basically what happens is you can click from list to manage then save (which goes back to the list page) then when you go to another 'manage' the CKEditor is initialised but it doesn't have any values in it, I've checked the update code (below) and 'value' definitely has the correct value but it's not getting pushed through to the CKEditor itself.
Perhaps it's a lack of understanding about the flow/initialisation process for CKEditor or a lack of understanding about knockout bindings or perhaps it's a problem with the framework that's been setup for our single page app - I'm not sure.
Here is the code:
//Test one for ckeditor
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
//handle destroying an editor (based on what jQuery plugin does)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
var existingEditor = CKEDITOR.instances[element.name];
existingEditor.destroy(true);
});
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
//handle programmatic updates to the observable
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
}
};
So in the HTML it's a fairly standard knockout 'data-bind: ckeditor' that applyies the bindings for it when the ViewModel is initialised.
I've put debugger; in the code to see the flow, it looks like when I load the first time it calls init, then update, when I go in the second time it hits the ko.utils.domNodeDisposal to dispose of the elements.
I've tried not destroying it which CKEditor then complains that something already exists with that name. I've tried not destroying it and checking for if it exists and initialising if it doesn't - that works the first time but the second time we have no CKEditor.
I figure there's just one thing I'm missing that will make it work but I've exhausted all options.
Does anyone have any knowledge on integrating these 3 things that can help me out?
Are there any knockout experts out there that might be able to help me out?
Any help would be much appreciated.
MD
For anyone interested I sorted it:
All it was was a basic order of execution, I just needed to set the value to the textarea html before it got initialised.
Note this uses a jquery adaptor extension to do the .ckeditor() on the element.
There is probably also a better way to do the 'blur' part.
This extension also doesn't work with options at the moment but that should be quite simple in comparison.
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
}
};
I've been working with this for a while now and ran again several problems with the .on("blur") approach. Namely, when people clicked in to the rich text and entered text then scrolled directly to the Save button on my form, the observable didn't get updated fast enough. There are a ton of ways to handle delays, but I wanted something more official. I dug in to the CKEditor documentation and found this gem: focusManager
This is built-in functionality that handles all the instances of focus and blur and allows you to hook up a true blur event to the control.
Here is my bindingHandler for rich text then
ko.bindingHandlers.richText = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var txtBoxID = $(element).attr("id");
var instance = CKEDITOR.instances[txtBoxID];
var options = allBindingsAccessor().richTextOptions || {};
options.toolbar_Full = [
['Source', '-', 'Format', 'Font', 'FontSize', 'TextColor', 'BGColor', '-', 'Bold', 'Italic', 'Underline', 'SpellChecker'],
['NumberedList', 'BulletedList', '-', 'Outdent', 'Indent', '-', 'Blockquote', 'CreateDiv', '-', 'JustifyLeft', 'JustifyCenter', 'JustifyRight', 'JustifyBlock', '-', 'BidiLtr', 'BidiRtl'],
['Link', 'Unlink', 'Image', 'Table']
];
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
if (CKEDITOR.instances[txtBoxID]) { CKEDITOR.remove(CKEDITOR.instances[txtBoxID]); };
});
$(element).ckeditor(options);
//wire up the blur event to ensure our observable is properly updated
CKEDITOR.instances[txtBoxID].focusManager.blur = function () {
var observable = valueAccessor();
observable($(element).val());
};
},
update: function (element, valueAccessor, allBindingsAccessor, viewModel) {
var val = ko.utils.unwrapObservable(valueAccessor());
$(element).val(val);
}
}
Building up on the work done in the other answers here's my solution:
handles changes using ckeditor's own change event (updates on keypress but not just that)
uses ckeditor's getData() so you don't get unwanted HTML like "magic line" and similar stuff
handles memory management (untested)
Code:
ko.bindingHandlers.ckeditor = {
init: function(element, valueAccessor, allBindingsAccessor, context) {
var options = allBindingsAccessor().ckeditorOptions || {};
var modelValue = valueAccessor();
var value = ko.utils.unwrapObservable(valueAccessor());
$(element).html(value);
$(element).ckeditor();
var editor = $(element).ckeditorGet();
//handle edits made in the editor
editor.on('change', function(e) {
var self = this;
if (ko.isWriteableObservable(self)) {
self($(e.listenerData).val());
}
}, modelValue, element);
//handle disposal (if KO removes by the template binding)
ko.utils.domNodeDisposal.addDisposeCallback(element, function() {
if (editor) {
CKEDITOR.remove(editor);
};
});
},
update: function(element, valueAccessor, allBindingsAccessor, context) {
// handle programmatic updates to the observable
var newValue = ko.utils.unwrapObservable(valueAccessor());
if ($(element).ckeditorGet().getData() != newValue)
$(element).ckeditorGet().setData(newValue)
}
};
The markup I use (note the afterkeydown):
<textarea
id="editor1"
data-bind="ckeditor: text, valueUpdate: 'afterkeydown'"
></textarea>
Update: as requested in the comments, here is a minimal working Fiddle.
First post so let me know if I've done anything wrong
In my project, I gave visual feedback as to whether there were unsaved changes and so needed the observable updated on keyup. And on click for when a toolbar button was clicked. This also was consistent with me using valueUpdate:['afterkeydown','propertychange','input'] in my data-bind attributes.
Also, for performance, I used the callback method parameter of .ckeditor(callback,options) rather than .on(eventName,handler).
This is the custom binding I came up with:
ko.bindingHandlers.ckeditor = {
init: function (element, valueAccessor, allBindingsAccessor, context) {
// get observable
var modelValue = valueAccessor();;
$(element).ckeditor(function(textarea) {
// <span> element that contains the CKEditor markup
var $ckeContainer = $(this.container.$);
// <body> element within the iframe (<html> is contentEditable)
var $editorBody =
$ckeContainer.find('iframe').contents().find('body');
// sets the initial value
$editorBody.html( modelValue() );
// handle edits made in the editor - by typing
$editorBody.keyup(function() {
modelValue( $(this).html() );
});
// handle edits made in the editor - by clicking in the toolbar
$ckeContainer.find('table.cke_editor').click(function() {
modelValue( $editorBody.html() );
});
});
// when ko disposes of <textarea>, destory the ckeditor instance
ko.utils.domNodeDisposal.addDisposeCallback(element, function () {
$(element).ckeditorGet().destroy(true);
});
},
update: function (element, valueAccessor, allBindingsAccessor, context) {
// handle programmatic updates to the observable
var newValue = ko.utils.unwrapObservable(valueAccessor());
var $ckeContainer = $(element).ckeditorGet().container;
if( $ckeContainer ) {
// <span> element that contains the CKEditor markup
$ckeContainer = $($ckeContainer.$);
// <body> element within the iframe (<html> is contentEditable)
var $editorBody =
$ckeContainer.find('iframe').contents().find('body');
// if new value != existing value, replace it in the editor
if( $editorBody.html() != newValue )
$editorBody.html( newValue );
}
}
};
Justification:
I know I should probably use .getData() and .setData(html) instead of this rather hacky way of finding <body> and <table class="cke_editor"> within the iframe contents.
Reason being, for update: the condition within:
if( $(element).ckeditorGet().getData() != newValue )
$(element).ckeditorGet().setData( newValue )
was initially true due to HTML formatting that CKEditor does. And so, notified the user about a dirty record even though it wasn't. Very specific to me so I thought you should know, in case you were wondering why.
I just used this technique with CKEditor 4 to overwrite the existing (1-way) "html" binding with a 2-way binding. I'm using the inline CKEditor which may behave differently (not sure) than the full/static editor. I started with the "value" binding and tweaked it to work with the innerHTML instead:
ko.bindingHandlers.html = {
'init': function (element, valueAccessor, allBindingsAccessor) {
var eventsToCatch = ["blur"];
var requestedEventsToCatch = allBindingsAccessor()["valueUpdate"];
var valueUpdateHandler = null;
if (requestedEventsToCatch) {
if (typeof requestedEventsToCatch == "string")
requestedEventsToCatch = [requestedEventsToCatch];
ko.utils.arrayPushAll(eventsToCatch, requestedEventsToCatch);
eventsToCatch = ko.utils.arrayGetDistinctValues(eventsToCatch);
}
valueUpdateHandler = function () {
var modelValue = valueAccessor();
var oldValue = ko.utils.unwrapObservable(modelValue);
var elementValue = element.innerHTML;
var valueHasChanged = (oldValue !== elementValue);
if (valueHasChanged)
modelValue(elementValue);
}
ko.utils.arrayForEach(eventsToCatch, function (eventName) {
var handler = valueUpdateHandler;
if (eventName.indexOf("after") == 0) {
handler = function () {
setTimeout(valueUpdateHandler, 0)
};
eventName = eventName.substring("after".length);
}
ko.utils.registerEventHandler(element, eventName, handler);
});
},
'update': function (element, valueAccessor) {
var newValue = ko.utils.unwrapObservable(valueAccessor());
var elementValue = element.innerHTML;
var valueHasChanged = (newValue !== elementValue);
if (valueHasChanged)
element.innerHTML = newValue;
}
};
Caveat: This should probably be updated to use CKEditor's own change event.
I rewrote this to update the observable on each keyup, instead of on blur. This is a lot more updates to the observable, but as long as you are saving with a button, this seems to work great so far!
//handle edits made in the editor
CKEDITOR.instances.thread_message.on('contentDom', function() {
CKEDITOR.instances.thread_message.document.on('keyup', function(e) {
var self = this;
if (ko.isWriteableObservable(self)) {
var ckValue = CKEDITOR.instances.element_id.getData();
self(ckValue);
//console.log("value: " + ckValue);
}
}, modelValue, element);
});
For the 'blur' part, I tried the code below and it seems to work
editor.on('blur', function (e) {
var self = this;
if (ko.isWriteableObservable(self)) {
var ckValue = e.editor.getData();
self(ckValue);
}
}, modelValue, element);
I think the 'Update' part is still needed if you "update" the observable from somewhere else (not through editing as this is taken care of by the 'blur')

Resources