Writing an escaped/unescaped Observable in Knockout - javascript

I have an issue where I want to store everything as quoted strings and display everything unquoted while in the form. My first solution was to just create two extra bindingHandlers to do this one for value and one for text.
ko.bindingHandlers.escapedValue = {
init : function (element, valueAccessor, allBindingsAccessor) {
var $element = $(element),
contentObservable = valueAccessor(),
currentTxt = ko.utils.unwrapObservable(contentObservable);
if (currentTxt) {
$element.val(unescape(currentTxt));
}
$element.change(function (e) {
contentObservable(escape($element.val()));
});
}
};
ko.bindingHandlers.escapedText = {
init : function (element, valueAccessor, allBindingsAccessor) {
var $element = $(element),
contentObservable = valueAccessor(),
currentTxt = ko.utils.unwrapObservable(contentObservable);
if (currentTxt) {
$element.text(unescape(currentTxt));
}
contentObservable.subscribe(function (newValue) {
$element.text(unescape(newValue));
});
}
};
However that gave me two issues 1) I do not get live updating anymore after key down 2) When I do some validation on the values for example character length it checks the length of the quoted string.
How can I write something like a ko.escapedObservable() or ko.subscribable.fn.escaped()
I have gotten closer but cant seem to get the saving correct. So now it displays properly and does the comparison properly but when I goto save it the values are still unescped
ko.escapedObservable = function (initialValue) {
var observableVal = ko.observable(initialValue),
result = ko.computed({
read: function () {
return unescape(observableVal());
},
write: function (newValue) {
return observableVal(escape(newValue));
}
});
this.toJSON = function () {
return escape(observableVal());
};
return result;
};
====EDIT====
Solution using two observables
// Escape and Unescape a text value
ko.escapedObservable = function (initialValue) {
var observableVal = ko.observable(initialValue),
result = ko.computed({
read: function () {
return observableVal();
},
write: function (newValue) {
observableVal(newValue);
}
});
result.unescaped = ko.computed({
read: function () {
return unescape(observableVal());
},
write: function (newValue) {
observableVal(escape(newValue));
}
});
return result;
};

ko.escapedObservable = function (initialValue) {
var observableVal = ko.observable(initialValue),
result = ko.computed({
read: function () {
return observableVal();
},
write: function (newValue) {
observableVal(newValue);
}
});
result.unescaped = ko.computed({
read: function () {
return unescape(observableVal());
},
write: function (newValue) {
observableVal(escape(newValue));
}
});
return result;
};

I'm having trouble figuring out exactly what you are asking. However, I believe you really just need a computed observable: [Example]
function viewModel() {
this.test = ko.observable('\"Something\"');
this.escapedTest = ko.computed(function () {
return escape(this.test());
}, this);
this.unescapedTest = ko.computed(function () {
return unescape(this.escapedTest());
}, this);
}

Related

How to return a list of available options in a select element in Protractor?

I am trying to retrieve all the options available in select element using Protractor. I am having trouble figuring out how to do this because I'm a JavaScript beginner and therefore am having trouble determining how to return the array and not a promise.
I've tried this:
getOptions = function () {
var self = this;
var availableOptions = [];
return this.selector.click().then(function () {
self.selectorOptions.then(function (options) {
options.forEach(function (option) {
option.getAttribute("value").then(function (value) {
availableOptions.push(value);
});
});
});
}).then(function () {
return availableOptions;
});
};
This returns a promise and not the array which I want. I also tried this:
getOptions = function () {
var self = this;
var availableOptions = [];
this.selector.click().then(function () {
self.cloudletTypeOptions.then(function (options) {
options.forEach(function (option) {
option.getAttribute("value").then(function (value) {
availableOptions.push(value);
});
});
});
});
return availableOptions;
};
When I do this and call the method from a test and try to print the result, it is 'undefined'. What do I need to do to ensure that the return value is defined and is an array? Barring that, how do I force the promise to resolve?
From what I understand, you are looking for map():
Apply a map function to each element within the ElementArrayFinder.
The callback receives the ElementFinder as the first argument and the
index as a second arg.
getOptions = function () {
var self = this;
return this.selector.click().then(function () {
return self.selectorOptions.map(function (option) {
return option.getAttribute("value").then(function (value) {
return value;
});
});
});
};

Integrating TextExt in SlickGrid custom editor

i want to implement a custom editor in SlickGrid using TextExt but I'm having trouble doing so.
I have two different lists args.column.names and `args.column.values.
I want the tags to show the selected names but to post the list of the corresponding ids.
Here is a first draft, I don't really see how to manage that.
Can someone help me figure out what to write in these functions to match what I'm trying to do ?
function AutoCompletedTextField(args) {
var $input;
var defaultValue;
var scope = this;
this.init = function () {
$input = $("<textarea class='textarea' rows='1'></textarea>")
.appendTo(args.container)
.bind("keydown.nav", function (e) {
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
e.stopImmediatePropagation();
}
})
.focus()
.select();
$('.textarea').textext({
plugins: 'tags autocomplete arrow'
}).bind('getSuggestions', function (e, data) {
var list = args.column.names,
textext = $(e.target).textext()[0],
query = (data ? data.query : '') || '';
$(this).trigger('setSuggestions', { result: textext.itemManager().filter(list, query) });
});
};
this.destroy = function () {
$input.remove();
};
this.focus = function () {
$input.focus();
};
this.getValue = function () {
return $input.textext()[0].hiddenInput().val();
};
this.setValue = function (val) {
$input.textext()[0].hiddenInput().val(val)
};
this.loadValue = function (item) {
$input[0].defaultValue = item[args.column.field];
$input.select();
};
this.serializeValue = function () {
return $input[0].defaultValue;
};
this.applyValue = function (item, state) {
item[args.column.field] = state;
};
this.isValueChanged = function () {
return (!($input.textext()[0].hiddenInput().val() == "" && defaultValue == null)) && ($input.textext()[0].hiddenInput().val() != defaultValue);
};
this.validate = function () {
if (args.column.validator) {
var validationResults = args.column.validator($input.val());
if (!validationResults.valid) {
return validationResults;
}
}
return {
valid: true,
msg: null
};
};
this.init();
}
You may want to try my repo, it's updated for the latest jQuery and has a lot of fixes and enhancements.
One of those enhancements is a Select2 editor, which is very similar to what you're trying to do. I think if you check that out it will be clear what is needed.
There's an example here.
As a bonus, there also a mechanism to allow certain keycodes to bubble through to the grid rather than be captured by the editor. This can be useful for up-arrow, etc. This isn't in the MLeibman branch.

rewriting localStorage javascript for chrome.local.set

I have this code which is working with the localStorage html5 calls. However it has to be rewritten for a Chrome Desktop app and I can't figure out how to port it over.
window.fakeStorage = {
_data: {},
setItem: function (id, val) {
return this._data[id] = String(val);
},
getItem: function (id) {
return this._data.hasOwnProperty(id) ? this._data[id] : undefined;
},
removeItem: function (id) {
return delete this._data[id];
},
clear: function () {
return this._data = {};
}
};
function LocalScoreManager() {
this.key = "bestScore";
var supported = this.localStorageSupported();
this.storage = supported ? window.localStorage : window.fakeStorage;
}
LocalScoreManager.prototype.localStorageSupported = function () {
var testKey = "test";
var storage = window.localStorage;
try {
storage.setItem(testKey, "1");
storage.removeItem(testKey);
return true;
} catch (error) {
return false;
}
};
LocalScoreManager.prototype.get = function () {
return this.storage.getItem(this.key) || 0;
};
LocalScoreManager.prototype.set = function (score) {
this.storage.setItem(this.key, score);
};
The error I get says "window.localStorage is not available in packaged apps. Use chrome.storage.local instead."
My attempt to rewrite it was this so far.. but it is breaking somewhere along the way.
$(document).ready(function() {
$("body").bind('keyup', function() {
var number = $(".best-container").val();
if(number == 'undefined'){
var number = "0";
}
chrome.storage.local.set({'bestScore': number});
});
chrome.storage.local.get('bestScore', function (result) {
hello= result.bestScore || 0;
$(".best-container").val(hello);
});
});
Porting localStorage to chrome.storage has one important pitfall: chrome.storage methods are asynchronous whereas localStorage access is synchronous.
That means: If you try to get a value from chrome.storage before the callback of the set method has been called, the value will still be undefined
Wrong way:
chrome.storage.local.set({'key': value});
...
chrome.storage.local.get('key', function(items) {
if(items.key) // won't be able to find the key
alert(items.key);
});
Correct way:
chrome.storage.local.set({'key': value}, function() {
...
chrome.storage.local.get('key', function(items) {
if(items.key)
alert(items.key); // will be "value"
});
});
or rather:
chrome.storage.local.set({'key': value}, function() {
doFurtherStuff();
});
function doFurtherStuff() {
...
chrome.storage.local.get('key', function(items) {
if(items.key)
alert(items.key); // will be "value"
});
}

Elegant way to wrap multiple Backbone render methods to avoid "before" and "after" event duplication?

I've divided the render() method on my View into two separate methods, in order to allow me to render the entire View, or just a small portion of the View, which is represented by an HTML form. The two render() methods look as such:
render: function () {
var templateData = _.extend({}, this.model.attributes, localizedText),
compiledLoadStopTemplate = Handlebars.compileClean(template);
this.isClosed = false;
this.triggerMethod("before:render", this);
this.triggerMethod("item:before:render", this);
this.$el.html(compiledLoadStopTemplate(JSON.parse(JSON.stringify(templateData))));
this.renderAppointmentForm();
this.bindUIElements();
this.showStatusNotification();
this.triggerMethod("render", this);
this.triggerMethod("item:rendered", this);
return this;
},
renderAppointmentForm: function () {
var templateData = _.extend({}, this.model.attributes, localizedText, { notification: this.notification }),
compiledAppointmentFormTemplate = Handlebars.compileClean(appointmentFormTemplate);
this.triggerMethod("before:render", this);
this.triggerMethod("item:before:render", this);
this.$el.find(".hook-appointment-form-container").html(compiledAppointmentFormTemplate(JSON.parse(JSON.stringify(templateData))));
this.bindUIElements();
this.showStatusNotification();
this.triggerMethod("render", this);
this.triggerMethod("item:rendered", this);
return this;
},
Now, there's obviously a boatload of duplicated code here; while the template data, template, and actual html() call are unique, almost all of the other lines are common between them.
It'd be nice to have a wrapped method that would allow me to supply the template data, compiled template, and html() lines, and automatically have the rest of the other before/after-firing methods in place universally, but I couldn't devise a method using Underscore's wrap() that really worked.
I'm sure there's a more advanced programming concept that fits this need perfectly, but it's avoiding my grasp right now.
What about extracting form in separate view? If its not possible then I have this suggestion:
loadStopTemplate: Handlebars.compileClean(template),
appointmentFormTemplate: Handlebars.compileClean(appointmentFormTemplate),
getTemplateAttributes: function () {
var attributes = _.extend({}, this.model.attributes, localizedText, { notification: this.notification });
return JSON.parse(JSON.stringify(attributes))
},
render: function () {
this.isClosed = false;
return this.enterRenderState(function (attributes) {
this.$el.html(this.loadStopTemplate(attributes));
this._renderForm(attributes);
})
},
renderAppointmentForm: function () {
return this.enterRenderState(this._renderForm)
},
_renderForm: function (attributes) {
this.$('.hook-appointment-form-container')
.html(this.appointmentFormTemplate(attributes))
return this;
},
enterRenderState: function (callback) {
this.triggerMethod("before:render", this);
this.triggerMethod("item:before:render", this);
callback.call(this, this.getTemplateAttributes());
this.bindUIElements();
this.showStatusNotification();
this.triggerMethod("render", this);
this.triggerMethod("item:rendered", this);
return this
}
One way you can easily refactor this is by creating a helper function that does all of the common functionality and then just pass in some functions for the parts that are different.
For example
render: function () {
var templateData = _.extend({}, this.model.attributes, localizedText),
compiledLoadStopTemplate = Handlebars.compileClean(template);
var self = this;
var isClosed = function () {
self.isClosed = false;
}
var renderHTML = function () {
self.$el.html(compiledLoadStopTemplate(JSON.parse(JSON.stringify(templateData))));
}
return this.renderHelper(renderHTML, isClosed);
}
renderAppointmentForm: function () {
var templateData = _.extend({}, this.model.attributes, localizedText, { notification: this.notification }),
compiledAppointmentFormTemplate = Handlebars.compileClean(appointmentFormTemplate);
var renderHTML = function () {
self.$el.find(".hook-appointment-form-container").html(compiledAppointmentFormTemplate(JSON.parse(JSON.stringify(templateData))));
}
return this.renderHelper(renderHTML);
}
renderHelper: function (renderHTML, isClosed) {
if (isClosed) {
isClosed();
}
this.triggerMethod("before:render", this);
this.triggerMethod("item:before:render", this);
renderHTML();
this.triggerMethod("render", this);
this.triggerMethod("item:rendered", this);
return this;
}

KnockoutJs computed array not calculated correctly

The code is as follows: EmployeeModel is the viewModel and the problem is that when I change an item's property - deletedFlag in employees (obs array), deletedItems is not updated.
How can i fix this?
function Employee(data) {
this.employeid = ko.observable(data.employeid);
this.name = ko.observable(data.name);
this.isactive = ko.observable(data.isactive);
this.deletedFlag = ko.observable(false);
}
var EmployeeModel = function () {
var self = this;
self.employees = ko.observableArray([]);
self.deletedItems = ko.computed(function () {
return ko.utils.arrayFilter(self.employees(), function (item) {
return item.deletedFlag == true;
});
}, this);
}
EDIT: and the following code marks one item from the array for deletion
self.removeEmployee = function (employee) {
employee.deletedFlag(true);
};
The property deletedFlag is an observable, therefore you need to retrieve its current value by invoking it as a function (you cannot compare it directly to any value):
self.deletedItems = ko.computed(function () {
return ko.utils.arrayFilter(self.employees(), function (item) {
return item.deletedFlag() == true; // <===
});
}, this);

Categories

Resources