Angular, config with dynamic data - javascript

See update below.
Original question:
This might be a fundemental question about Angular. But here is the full context.
I use the Angular plugin TextAngular (1.2.2). I am trying to extend the toolbars with extra buttons. Extending the toolbar in code (client side) works rather well (se snippet below).
But i want to create buttons which i define serverside - thus they have to be downloaded to the client. When i introduce an async call (by using a service) and try to inject the config in the callback, the toolbar buttons does not show up. I suspect that this is because the angular engine needs to configure the textangular plugin, before instantiating. I have tried to create a provider, instead of a service, and feed that to the .config(), but then i get an exception about the provider not being found.
The static approach works rather well. But how should i approach this with dynamic data?
//Module
var myApp = angular.module('myApp', ['textAngular']);
//Configuration
myApp.config(function($provide) {
$provide.decorator('taOptions', ['taRegisterTool', '$delegate',
function(taRegisterTool, taOptions) {
//I found these helpers somewhere
var insertToolHelper = {
insertTextAtCursor: function(text) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
}
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().text = text;
}
},
moveCaret: function(charCount) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
var textNode = sel.focusNode;
sel.collapse(textNode.nextSibling, charCount);
}
} else if ((sel = window.document.selection)) {
if (sel.type != "Control") {
range = sel.createRange();
range.move("character", charCount);
range.select();
}
}
}
};
var customToolbarElements = [{
value: "$name",
description: "Name",
faIcon: "fa fa-user"
}, {
value: "$today",
description: "Date",
faIcon: "fa fa-calendar"
}];
taOptions.toolbar.push([]); //Tilføj ny toolbar
angular.forEach(customToolbarElements, function(item) {
taRegisterTool(item.description, {
iconclass: item.faIcon,
tooltiptext: item.description,
action: function() {
insertToolHelper.insertTextAtCursor(item.value);
return insertToolHelper.moveCaret(1);
}
});
// register the tool with textAngular
taOptions.toolbar[4].push(item.description);
}, this);
return taOptions;
}
]);
});
Update:
Based on Simeons post, i managed to get the dynamic toolbuttons like this:
//Module
var myApp = angular.module('myApp', ['textAngular']);
//Service
myApp.service('ConfigurationData', [
'$http', '$q', function (http, q) {
var deferredConfig = q.defer();
//This method returns someting like [{ value: "name", description: "Person name", faIcon: "fa fa-user" }];
http.get(location.pathname + '/api/templates').success(function (data) {
return deferredConfig.resolve(data);
});
return {
getConfig: function () {
return deferredConfig.promise;
}
};
}
]);
//Controller
myApp.controller('SettingsCtrl', [
'$scope', 'textAngularManager', 'ConfigurationData',
function ($scope, $rootScope, textAngularManager, configurationData) {
var insertToolHelper = {
insertTextAtCursor: function (text) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.getRangeAt && sel.rangeCount) {
range = sel.getRangeAt(0);
range.deleteContents();
range.insertNode(document.createTextNode(text));
}
} else if (document.selection && document.selection.createRange) {
document.selection.createRange().text = text;
}
},
moveCaret: function (charCount) {
var sel, range;
if (window.getSelection) {
sel = window.getSelection();
if (sel.rangeCount > 0) {
var textNode = sel.focusNode;
sel.collapse(textNode.nextSibling, charCount);
}
} else if ((sel = window.document.selection)) {
if (sel.type != "Control") {
range = sel.createRange();
range.move("character", charCount);
range.select();
}
}
}
};
configurationData.getConfig().then(function (config) {
var customToolbarElements = config.jsonArrayFromService;
angular.forEach(customToolbarElements, function (item) {
var desc = item.description ? item.description : "unknown tool";
textAngularManager.addTool(desc, {
iconclass: item.faIcon ? item.faIcon : "fa fa-gear",
tooltiptext: desc,
action: function () {
insertToolHelper.insertTextAtCursor(item.value);
return insertToolHelper.moveCaret(1);
}
});
}, this);
});
}
]);

You want to have a look at the textAngularManager service, the two functions you'll want to look at are:
// toolkey, toolDefinition are required. If group is not specified will pick the last group, if index isnt defined will append to group
textAngularManager.addTool(toolKey, toolDefinition, group, index)
// adds a Tool but only to one toolbar not all
textAngularManager.addToolToToolbar(toolKey, toolDefinition, toolbarKey, group, index)
They don't need to be called in the provider so you can add Tools at any time (there is also a removeTool function as well).
The problem you faced is that config is only called once when the module is loaded.

Related

executeScript not work

I have some JS-scripts in my tests. I don't understand why, but it stoped working now.
Maybe it happened after protractor updating (to version 3.3.0).
Maybe somebody know what may happend?
My scripts:
PsComponent.prototype.getHighlightedText = function () {
return browser.executeScript_(function getSelectionText() {
var text = "";
if (window.getSelection) {
text = window.getSelection().toString();
} else if (document.selection && document.selection.type != "Control") {
text = document.selection.createRange().text;
}
return text;
});
};
Result:
nothing
And:
PsComponent.prototype.getCaretPosition = function () {
return browser.executeScript(function (input) {
if (document.selection && document.selection.createRange) {
var range = document.selection.createRange();
var bookmark = range.getBookmark();
var caret_pos = bookmark.charCodeAt(2) - 2;
} else {
if (input.setSelectionRange){
caret_pos = input.selectionStart;
}
}
return caret_pos;
});
};
Result:
- Failed: JavaScript error (WARNING: The server did not provide any stacktrace information)
Not directly answering the question, but here are the similar functions we are using (I guess things like that would naturally come up in any browser test automation project):
this.getCaretPosition = function (elm) {
return browser.executeScript(function () {
var webElement = arguments[0];
return webElement.value.slice(0, webElement.selectionStart).length;
}, elm.getWebElement())
};
this.getInputSelection = function (elm) {
return browser.executeScript(function () {
var webElement = arguments[0];
return webElement.value.substring(webElement.selectionStart, webElement.selectionEnd);
}, elm.getWebElement())
};
Usage samples:
expect(helpers.getCaretPosition(amountInput)).toEqual(1);
expect(helpers.getInputSelection(amountInput)).toEqual("-100.00");

Textarea selection

I want to make HTML5 datepicker like control. (http://www.w3schools.com/html/tryit.asp?filename=tryhtml5_input_type_date). For this purpose I want to prevent any user selection while I'm still able to make selection through code. I prevent selection on mousedown and selectstart events, but after that I can't get the right cursor position. Can anyone give me directions or samples that do the work
Example code (with ext core framework):
this.root.on('mousedown', function (evt, el) {
me.select(el);
evt.preventDefault();
});
select : function(el) {
var cur = this.getCursorPos(el);
var section = this.modes[this.format].getSectionByPos(cur);
this.setTextSelection(el, section.start, section.end);
},
getCursorPos : function (input) {
if ("selectionStart" in input && document.activeElement == input) {
return {
start: input.selectionStart,
end: input.selectionEnd
};
}
else if (input.createTextRange) {
// code that supports other browser
}
return -1;
}
setTextSelection : function(el, start, end) {
if ("selectionStart" in el) {
setTimeout(function() {
el.selectionStart = start;
el.selectionEnd = end;
}, 1);
}
else if (el.createTextRange) {
// code that supports other browser
}
},

Bad escaping of EOL

I am trying to work with a simple WYSIWYG editor. JSLint is saying it has "Bad escaping of EOL". Since I am new to javascript I am having a hard time figuring out what it means, since I am working with code found online. Can anyone tell me please what I should be doing instead of ending the line with a slash?
Here is the code in question: http://jsfiddle.net/spadez/KSA5e/9/
/*
* WYSIWYG EDITOR BASED ON JQUERY RTE
*/
// define the rte light plugin
(function ($) {
if (typeof $.fn.rte === "undefined") {
var defaults = {
content_css_url: "rte.css",
dot_net_button_class: null,
max_height: 350
};
$.fn.rte = function (options) {
$.fn.rte.html = function (iframe) {
return iframe.contentWindow.document.getElementsByTagName("body")[0].innerHTML;
};
// build main options before element iteration
var opts = $.extend(defaults, options);
// iterate and construct the RTEs
return this.each(function () {
var textarea = $(this);
var iframe;
var element_id = textarea.attr("id");
// enable design mode
function enableDesignMode() {
var content = textarea.val();
// Mozilla needs this to display caret
if ($.trim(content) === '') {
content = '<br />';
}
// already created? show/hide
if (iframe) {
console.log("already created");
textarea.hide();
$(iframe).contents().find("body").html(content);
$(iframe).show();
$("#toolbar-" + element_id).remove();
textarea.before(toolbar());
return true;
}
// for compatibility reasons, need to be created this way
iframe = document.createElement("iframe");
iframe.frameBorder = 0;
iframe.frameMargin = 0;
iframe.framePadding = 0;
iframe.height = 200;
if (textarea.attr('class')) iframe.className = textarea.attr('class');
if (textarea.attr('id')) iframe.id = element_id;
if (textarea.attr('name')) iframe.title = textarea.attr('name');
textarea.after(iframe);
var css = "";
if (opts.content_css_url) {
css = "<link type='text/css' rel='stylesheet' href='" + opts.content_css_url + "' />";
}
var doc = "<html><head>" + css + "</head><body class='frameBody'>" + content + "</body></html>";
tryEnableDesignMode(doc, function () {
$("#toolbar-" + element_id).remove();
textarea.before(toolbar());
// hide textarea
textarea.hide();
});
}
function tryEnableDesignMode(doc, callback) {
if (!iframe) {
return false;
}
try {
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(doc);
iframe.contentWindow.document.close();
} catch (error) {
console.log(error);
}
if (document.contentEditable) {
iframe.contentWindow.document.designMode = "On";
callback();
return true;
} else if (document.designMode !== null) {
try {
iframe.contentWindow.document.designMode = "on";
callback();
return true;
} catch (error) {
console.log(error);
}
}
setTimeout(function () {
tryEnableDesignMode(doc, callback);
}, 500);
return false;
}
function disableDesignMode(submit) {
var content = $(iframe).contents().find("body").html();
if ($(iframe).is(":visible")) {
textarea.val(content);
}
if (submit !== true) {
textarea.show();
$(iframe).hide();
}
}
// create toolbar and bind events to it's elements
function toolbar() {
var tb = $("<div class='rte-toolbar' id='toolbar-" + element_id + "'><div>\
<p>\
<a href='#' class='bold'>Bold</a>\
<a href='#' class='italic'>Italic</a>\
<a href='#' class='unorderedlist'>List</a>\
</p></div></div>");
$('.bold', tb).click(function () {
formatText('bold');
return false;
});
$('.italic', tb).click(function () {
formatText('italic');
return false;
});
$('.unorderedlist', tb).click(function () {
formatText('insertunorderedlist');
return false;
});
// .NET compatability
if (opts.dot_net_button_class) {
var dot_net_button = $(iframe).parents('form').find(opts.dot_net_button_class);
dot_net_button.click(function () {
disableDesignMode(true);
});
// Regular forms
} else {
$(iframe).parents('form').submit(function () {
disableDesignMode(true);
});
}
var iframeDoc = $(iframe.contentWindow.document);
var select = $('select', tb)[0];
iframeDoc.mouseup(function () {
setSelectedType(getSelectionElement(), select);
return true;
});
iframeDoc.keyup(function () {
setSelectedType(getSelectionElement(), select);
var body = $('body', iframeDoc);
if (body.scrollTop() > 0) {
var iframe_height = parseInt(iframe.style['height']);
if (isNaN(iframe_height)) iframe_height = 0;
var h = Math.min(opts.max_height, iframe_height + body.scrollTop()) + 'px';
iframe.style['height'] = h;
}
return true;
});
return tb;
}
function formatText(command, option) {
iframe.contentWindow.focus();
try {
iframe.contentWindow.document.execCommand(command, false, option);
} catch (e) {
//console.log(e)
}
iframe.contentWindow.focus();
}
function setSelectedType(node, select) {
while (node.parentNode) {
var nName = node.nodeName.toLowerCase();
for (var i = 0; i < select.options.length; i++) {
if (nName == select.options[i].value) {
select.selectedIndex = i;
return true;
}
}
node = node.parentNode;
}
select.selectedIndex = 0;
return true;
}
function getSelectionElement() {
if (iframe.contentWindow.document.selection) {
// IE selections
selection = iframe.contentWindow.document.selection;
range = selection.createRange();
try {
node = range.parentElement();
} catch (e) {
return false;
}
} else {
// Mozilla selections
try {
selection = iframe.contentWindow.getSelection();
range = selection.getRangeAt(0);
} catch (e) {
return false;
}
node = range.commonAncestorContainer;
}
return node;
}
// enable design mode now
enableDesignMode();
}); //return this.each
}; // rte
} // if
$(".rte-zone").rte({});
})(jQuery);
EDIT: For bonus marks there are also two other errors which I haven't been able to squish -
Missing radix parameter
Height is better written in dot notation
JS didn't support end-of-line escaping with \ until ES5 - you can use multiple strings with a + operator instead, i.e.
"string 1" +
"string 2" +
"string 3"
Re: your other questions:
Use parseInt(n, 10) to force base (aka radix) 10, i.e. decimal
Use iframe.style.height instead of iframe.style['height']
You have two options:
1) activate multistr: true as suggested by #csharpfolk. (You can do it at file level by adding /*jshint multistr: true */ or add it in your linter config file (.jshintrc, .eslintrc, etc.)).
2) Replace your multistring as suggested by #Altinak or use an array and join:
["string 1",
"string 2",
"string 3",
].join('')

DOM object only requires two clicks in Internet Explorer

I'm really struggling with the following bit of code. I'm still really new to using DOM with Javascript and this script is running flawlessly in FireFox, Chrome and Safari. In Internet Explorer it requires two clicks. If you visit the link in FireFox and then the same link in Internet Explorer you'll see that if you click a shape in FireFox it immediately shows the colour options if you do this in Internet Explorer it will not show the colour options until you've clicked on the shape twice or on a shape and then another shape. Can an IE, DOM, Javascript Ninja tell me what's wrong with the script that cause the need for two clicks in IE?
<?php
$swatches = $this->get_option_swatches();
?>
<script type="text/javascript">
document.observe('dom:loaded', function() {
try {
var swatches = <?php echo Mage::helper('core')->jsonEncode($swatches); ?>;
function find_swatch(key, value) {
for (var i in swatches) {
if (swatches[i].key == key && swatches[i].value == value)
return swatches[i];
}
return null;
}
function has_swatch_key(key) {
for (var i in swatches) {
if (swatches[i].key == key)
return true;
}
return false;
}
function create_swatches(label, select) {
// create swatches div, and append below the <select>
var sw = new Element('div', {'class': 'swatches-container'});
select.up().appendChild(sw);
// store these element to use later for recreate swatches
select.swatchLabel = label;
select.swatchElement = sw;
// hide select
select.setStyle({position: 'absolute', top: '-9999px'});
$A(select.options).each(function(opt, i) {
if (opt.getAttribute('value')) {
var elm;
var key = trim(opt.innerHTML);
// remove price
if (opt.getAttribute('price')) key = trim(key.replace(/\+([^+]+)$/, ''));
var item = find_swatch(label, key);
if (item)
elm = new Element('img', {
src: '<?php echo Mage::getBaseUrl(Mage_Core_Model_Store::URL_TYPE_MEDIA); ?>swatches/'+item.img,
alt: opt.innerHTML,
title: opt.innerHTML,
'class': 'swatch-img'});
else {
console.debug(label, key, swatches);
elm = new Element('a', {'class': 'swatch-span'});
elm.update(opt.innerHTML);
}
elm.observe('click', function(event) {
select.selectedIndex = i;
fireEvent(select, 'change');
var cur = sw.down('.current');
if (cur) cur.removeClassName('current');
elm.addClassName('current');
});
sw.appendChild(elm);
}
});
}
// Hide Second Option's Label
function hideStuff(id) {
if (document.getElementById(id)) {
document.getElementById(id).style.display = 'none';
}
}
hideStuff("last-option-label");
function showStuff(id) {
if (document.getElementById(id)) {
document.getElementById(id).style.display = '';
}
}
function recreate_swatches_recursive(select) {
// remove the old swatches
if (select.swatchElement) {
select.up().removeChild(select.swatchElement);
select.swatchElement = null;
}
// create again
if (!select.disabled)
showStuff("last-option-label");
create_swatches(select.swatchLabel, select);
// recursively recreate swatches for the next select
if (select.nextSetting)
recreate_swatches_recursive(select.nextSetting);
}
function fireEvent(element,event){
if (document.createEventObject){
// dispatch for IE
var evt = document.createEventObject();
return element.fireEvent('on'+event, evt);
}
else{
// dispatch for firefox + others
var evt = document.createEvent("HTMLEvents");
evt.initEvent(event, true, true ); // event type,bubbling,cancelable
return !element.dispatchEvent(evt);
}
}
function trim(str) {
return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
}
$$('#product-options-wrapper dt').each(function(dt) {
// get custom option's label
var label = '';
$A(dt.down('label').childNodes).each(function(node) {
if (node.nodeType == 3) label += node.nodeValue;
});
label = trim(label);
var dd = dt.next();
var select = dd.down('select');
if (select && has_swatch_key(label)) {
create_swatches(label, select);
// if configurable products, recreate swatches of the next select when the current select change
if (select.hasClassName('super-attribute-select')) {
select.observe('change', function() {
recreate_swatches_recursive(select.nextSetting);
});
}
}
});
}
catch(e) {
alert("Color Swatches javascript error. Please report this error to support#galathemes.com. Error:" + e.message);
}
});
</script>
console.debug has been deprecated. In the "function create_swatches(label, select)" where it is written "console.debug(label, key, swatches);" change it to "console.log(label, key, swatches);" or you can just delete that line of code all together....... thaterrorbegone

How do I subclass the TextCellEditor of Slickgrid?

How do I subclass the TextCellEditor of Slickgrid?
I want to style the editors used in slickgrid using the uniform library; to do so I need to call
$("input, textarea, select, button").uniform();
after the html is generated; in other words after the editor object's init function is called; Currently I just copy the entire editor source code and add that line just before the end of the init function. I just seems non elegant.
edit:
to be clear to people unfamiliar with slickgrid, here is the code:
var myTextCellEditor = function(args) {
var $input;
var defaultValue;
var scope = this;
this.init = function() {
$input = $("<INPUT type=text class='editor-text' />")
.appendTo(args.container)
.bind("keydown.nav", function(e) {
if (e.keyCode === $.ui.keyCode.LEFT || e.keyCode === $.ui.keyCode.RIGHT) {
e.stopImmediatePropagation();
}
})
.focus()
.select();
$input.uniform();
};
this.destroy = function() {
$input.remove();
};
this.focus = function() {
$input.focus();
};
this.loadValue = function(item) {
defaultValue = item[args.column.field] || "";
$input.val(defaultValue);
$input[0].defaultValue = defaultValue;
$input.select();
};
this.serializeValue = function() {
return $input.val();
};
this.applyValue = function(item,state) {
item[args.column.field] = state;
};
this.isValueChanged = function() {
return (!($input.val() == "" && defaultValue == null)) && ($input.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();
}
Where $input.uniform(); is the only line that is different from the default TextCellEditor.
You could bind a .ready() event to the generated html content by class using .live() so it applies to all future content, not just content present in the initial DOM.
So...
$('.classOfSlickGrid').live('ready', function() {
$("input, textarea, select, button").uniform();
}

Categories