Updates at the bottom
I wrote a filter to manage what I displace in my ng-repeat. However, when the data being returned should be an empty array, in IE 9+ I get a crash, but in FF and Chrome it works fine.
BuildFilter below is called from angular.module('app').filter(filterId, buildFilter);
function buildFilter() {
return function (input, limitTo, keyWords) {
var outputPre = [];
var outputPost = [];
var d = new Date();
console.log('filter event: '
+ d.getHours() + ":"
+ d.getMinutes() + ":"
+ d.getSeconds());
var outputPre = [];
if (!(limitTo === null
|| limitTo === undefined
|| limitTo === '')) {
for (var i = 0; i < input.length; i++) {
if (input[i] !== null && input[i] !== undefined) {
switch (limitTo) {
case 'edi':
if (input[i].dateReleased === null) {
outputPre.push(input[i]);
}
break;
case 'rel':
if (input[i].dateReleased !== null
&& input[i].dateRetired === null) {
outputPre.push(input[i]);
}
break;
case 'ret':
if (input[i].dateRetired !== null) {
outputPre.push(input[i]);
}
break;
default:
outputPre.push(input[i]);
}
}
}
}
else {
for (var i = 0; i < input.length; i++) {
outputPre.push(input[i]);
}
}
//Sanity Check Log Entry
console.log('pre count: ' + outputPre.length);
if (!(keyWords === null
|| keyWords === undefined
|| keyWords === '')) {
var tkw = keyWords.toLocaleLowerCase();
for (var i = 0; i < outputPre.length; i++) {
var tn = outputPre[i].name.toLocaleLowerCase();
if (tn.indexOf(tkw) > -1) {
outputPost.push(outputPre[i]);
}
}
}
else {
for (var i = 0; i < outputPre.length; i++) {
outputPost.push(outputPre[i]);
}
}
//Sanity Check Log Entry
console.log('post count: ' + outputPost.length);
return outputPost;
};
};
My sample data is:
var data= [ //for input
{
id: 0,
dateCreated: '1/1/2014',
dateReleased: null,
dateRetired: null,
name: 'Sample Data',
},
{
id: 1,
dateCreated: '1/1/2014',
dateReleased: null,
dateRetired: null,
name: 'Other Sample Data',
},
]
limitTo supports the following values: 'edi' 'rel', 'ret'
keyWords is just any string. It just looks to see if any part of the string is in the name field.
Update to post:
Reduced the code to the following.
function isEdi(obj) {
if ((obj.dateReleased === null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
function isRel(obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
function isRet(obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired !== null)) {
return true;
}
return false;
}
function buildFilter() {
return function (input, limitTo) {
var output = [];
switch (limitTo) {
case 'edi':
output = input.filter(isEdi);
break;
case 'rel':
output = input.filter(isRel);
break;
case 'ret':
output = input.filter(isRet);
break;
default:
output = input;
}
return output;
};
};
IE crashes when the return is [], but works fine if the return as at least 1 record.
2ns Update to post:
Changed the repeat to ng-repeat="obj in objlist | filter:getObJFilter(objFilter) | orderBy:['+dateRetired','+dateReleased','+name']">
objFilter is a parameter to get the filter function to pass to filter. This way I am just extending the native filter rather than creating a new filter.
So my controller contains the following.
$scope.getFilter = function (val) {
switch (val) {
case 'edi':
return isEdi();
break;
case 'rel':
return isRel();
break;
case 'ret':
return isRet();
break;
default:
return function (obj) {return true };
}
}
function isEdi() {
return function(obj){
if ((obj.dateReleased === null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}}
function isRel() {
return function (obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired === null)) {
return true;
}
return false;
}
}
function isRet() {
return function (obj) {
if ((obj.dateReleased !== null)
&& (obj.dateRetired !== null)) {
return true;
}
return false;
}
}
I believe that I have done everything I can to eliminate the filter as being the problem. So I am now to where I believe there is a problem with an empty set being rendered in IE.
I will post a full test example in Plunkr tonight.
Note: I am also using AngularUI and Angular for UI Bootstrap. Shortcut for getting needed directives. However, I have had problems with UI Bootstrap on other issues, so I am going to replace a few more of their components to isolate the problem some more.
3rd Update I have removed all 3rd party directives. I did have UI Bootstrap to support TBS, so I eliminated that to save my sanity.
Put
<html xmlns:ng="http://angularjs.org">
or
<html xmlns:ng="http://angularjs.org" id="ng-app" ng-app="optionalModuleName">
Follow this
This isn't a problem with Angular.js, but with the UI.Bootstrap directives.
Since I couldn't replicate the issue in Plunker, I started to look at the other directives. Eliminated the tab control out of the SPA, and replaced it with my own custom tabs based on TBS. Problem went away.
Further investigation reveals that this may be caused by a recursion problem in the UI.Bootstrap tab. So I will document my findings, and post to that GitHub.
I wouldn't have found this without the simple suggestion of posting code to Plunker, so I am the angular crew again.
My UI looked like:
<tabset>
<tab header="some header">
<div ng-repeat="...">
...
So the tabset and tab directive were getting fired at every change in my filter on the repeat. So I have removed the tabset and tab, and just replaced them with the standard TBS tabs.
Related
I've been trying to do some html and css but I'm really bad at this so far. I was using a function that would check if two selectors match. A friend of mine came up with this code but neither of us fully understands how the return of the "tag.class" case works. My question is, if it doesn't divide the newSelector, how can it succesfully check the tag and class?
var matchFunctionMaker = function(selector) {
var selectorType = selectorTypeMatcher(selector);
var matchFunction;
if (selectorType === "id") {
matchFunction = nuevoSelector => '#' + nuevoSelector.id === selector;
} else if (selectorType === "class") {
matchFunction = nuevoSelector => {
var lista = nuevoSelector.classList;
for (let x of lista) {
if (selector === '.' + x) return true;
}
return false;
}
} else if (selectorType === "tag.class") {
matchFunction = nuevoSelector => {
var [tag, clase] = selector.split('.');
return matchFunctionMaker(tag) (nuevoSelector) && matchFunctionMaker(`.${clase}`) (nuevoSelector);
};
} else if (selectorType === "tag") {
matchFunction = nuevoSelector => nuevoSelector.tagName.toLowerCase() === selector.toLowerCase();
}
return matchFunction;
};
Thanks in advance!
I have a set of javascript functions that are working correctly in IE11 but not in Edge and I am not certain what I need to amend to sort them out. I am calling them using the Id of the select box
function getComboValue (combo)
{
if (typeof (combo) == "string")
combo = getComboById (combo);
// If no combo can be found return undefined
if (!combo)
return;
if (isDHTMLCombo (combo))
{
var sVal = combo.getActualValue ();
if ((sVal != null) && (sVal != 'null'))
return sVal;
}
else
{
var selIdx = combo.selectedIndex;
if (selIdx > -1)
{
var selValue = combo.options[selIdx].value;
return selValue;
}
}
return '';
}
function getComboById (id)
{
var combo = document.getElementById (id);
if (combo && combo.tagName == "INPUT")
{
// DHTML combo is returned if the id is of a DHTML combo box.
var par = combo.parentNode;
if (par)
{
// Get the parent object
combo = par.combo;
}
}
return combo;
}
function isDHTMLCombo (combo)
{
if (typeof (combo) == "string")
combo = getComboById (combo);
if (!combo)
return false;
if (combo.DOMelem_input != undefined)
{
// Seems to be DHTML
return true;
}
if (combo.getAttribute ("igIsDHTML") != null)
{
// Seems to be DHTML
return true;
}
return false;
}
How can I adapt these so that will return the correct information in Edge - getComboValue is returning "" at the moment
I got these two functions, and they work great.
But since I only call global.replaceFields from global.translateAll then I want to get rid of global.replaceFields and put its functionality inside global.translateAll
How would you go about merging global.replaceFields into global.translateAll without losing the current functionality?
Thanks :)
// Translate everything in that field
global.translateAll = (textfield, usersLanguage) => {
for (var property in textfield) {
if (!textfield.hasOwnProperty(property)) {
return false;
} else if (typeof textfield[property] !== "object") {
textfield[property] = global.replaceFields(textfield[property], usersLanguage);
} else {
global.translateAll(textfield[property], usersLanguage);
}
}
}
// Translate everything in that field
global.replaceFields = (textfield, usersLanguage) => {
// Keep running until all fields are replaced
while (textfield.indexOf("{{") != -1) {
// isolate the field
let fromField = textfield.substring((textfield.indexOf("{{") + 2), (textfield.indexOf("}}")));
let toField = ""
// If its a translated text
if (fromField.indexOf("trans") != -1) {
toField = usersLanguage[fromField];
textfield = textfield.replace("{{" + fromField + "}}", toField);
}
}
return (textfield);
}
This should work
global.translateAll = (textfield, usersLanguage) => {
var replaceFields = (textfield, usersLanguage) => {
// Keep running until all fields are replaced
while (textfield.indexOf("{{") != -1) {
// isolate the field
let fromField = textfield.substring((textfield.indexOf("{{") + 2), (textfield.indexOf("}}")));
let toField = ""
// If its a translated text
if (fromField.indexOf("trans") != -1) {
toField = usersLanguage[fromField];
textfield = textfield.replace("{{" + fromField + "}}", toField);
}
}
return (textfield);
}
for (var property in textfield) {
if (!textfield.hasOwnProperty(property)) {
return false;
} else if (typeof textfield[property] !== "object") {
textfield[property] = replaceFields(textfield[property], usersLanguage);
} else {
global.translateAll(textfield[property], usersLanguage);
}
}
}
I'm adding some items to localStorage, using jQuery/JS, which is all fine but in trying to remove a specific item within the array (if it's the same item) is proving difficult.
In my console logs (for testing) it seems to clear the [Object] but it's not updating the key. Perhaps my hierarchy is wrong... any ideas?
//
function addToStorage(elem, name) {
localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == 'null' || !localData) {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
typefaceID = elem.find('input').val();
typefaceName = elem.find('input').attr('data-name');
typefacePrice = elem.find('input').attr('data-price');
typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
$(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
This is an example of the localData once it's been added to.
[
{
"id":"dr-raymond-desktop-40374",
"name":"DR-Raymond",
"format":"Desktop (OTF)",
"price":"15.00",
"qty":1
},
{
"id":"dr-raymond-webfont-39949",
"name":"DR-Raymond",
"format":"Webfont (WOFF)",
"price":"15.00",
"qty":1
}
]
Never add/remove elements from an array while iterating over it using "foreach". Instead, try this:
for (index = typefaces.length - 1; index >= 0; index--){
value = typefaces[index];
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
});
Another way to do this more elegantly is by using ES6 filter():
typefaces = typefaces.filter(value => !(value && value.id == typefaceID && name == "drf"));
Also, you are comparing localData to the literal string 'null' which is kind of pointless. Your second condition - if (!localData) is enough in this case, and will handle it properly.
The problem lies in your splice method usage. Note that, according to MDN splice modifies the array in place and returns a new array containing the elements that have been removed. So when using a loop and trying to remove the some elements, splice will make shifting of elements. This is because iterating incrementally through the array, when you splice it, the array is modified in place, so the items are "shifted" and you end up skipping the iteration of some. Looping backwards fixes this because you're not looping in the direction you're splicing.
Solution 1
Loop backwards while splicing.
for(var i = typefaces.length; i--;)
{
if (typefaces[i] !== null)
{
if (typefaces[i] == typefaceID)
{
typefaces.splice(i, 1);
}
}
}
Working bin link here.
Solution 2
Its usually faster to generate a new array instead of modifying the existing one. So, your code will look like
ty = [];
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id != typefaceID) {
ty.push(value);
}
}
});
typefaces = ty;
Working bin link is here.
After that there is no problem found in getting and setting your localStorage.
You have an additional error in your code, you write
if (value !== null) {
if (value.id == typefaceID) {
// name will always have the value drf
// since you pass it in your function
// when you call it addToStorage(addTo, 'drf');
if (name == 'drf') {
typefaces.splice(index, 1);
}
}
}
when it should be
if (value !== null) {
if (value.id == typefaceID) {
// here the comparison should be between value.name and name
if (value.name == name) {
typefaces.splice(index, 1);
}
}
}
which can be also written
if (value !== null) {
if (value.id == typefaceID && value.name == name) {
typefaces.splice(index, 1);
}
}
You are mutating the array by splicing it. The only localstorage items that are being re written are the ones that are left in the array.
So delete the item from the storage when you splice an object from the array.
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
var removedTypeFace = typefaces.splice(index, 1);
//console.log(typefaces);
if(removedTypeFace) {
localStorage.removeItem(removedTypeFace.name + 'Storage');
}
}
}
}
});
OK. You're going to kick yourself. The first time you pull from localStorage, the getItem method DOES return a null value, but you're trying to compare it to a string ('null', in quotes). Fail. I changed localData == 'null' to localData == null.
The second part of the expression tries to see the value returned from getItem is actually defined, which is good. I changed that to be more explicit, by way of typeof.
Explicit variable declarations help, too.
function addToStorage(elem, name) {
var localData = localStorage.getItem(name + 'Storage');
var typefaces;
if (localData == null || typeof(localData) == 'undefined') {
typefaces = [];
} else {
typefaces = JSON.parse(localData);
}
var typefaceID = elem.find('input').val();
var typefaceName = elem.find('input').attr('data-name');
var typefacePrice = elem.find('input').attr('data-price');
var typefaceQty = 1;
$.each(typefaces, function(index, value) {
if (value !== null) {
if (value.id == typefaceID) {
if (name == 'drf') {
//console.log(index);
//console.log(typefaces);
typefaces.splice(index, 1);
//console.log(typefaces);
}
}
}
});
typefaces.push({
'id': typefaceID,
'name': typefaceName,
'price': typefacePrice,
'qty': typefaceQty
});
localStorage.setItem(name + 'Storage', JSON.stringify(typefaces));
}
//
jQuery(document).on('click', 'summary.cart td.font', function(e) {
e.preventDefault();
addTo = $(this);
addTo.each(function() {
addToStorage(addTo, 'drf');
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<summary class='cart'><table><tr>
<td class='font' >Click Here for Item 1
<input data-name='abc' data-price='2.00' value="Item 1" /></td></tr>
<tr>
<td class='font' >Click Here for Item 2
<input data-name='def' data-price='4.00' value="Item 2" /></td></tr></table>
</summary>
I have searched high and low on the Interwebs, and found some really awesome JS code editors with syntax highlighting and indentation and more... but none seem to have support for Smarty template tags yet.
A new Smarty mode for CodeMirror would be the best, but I'll use a different editor if I need to.
I did find this blog post... but it is VERY simple, and I would like to still support mixed HTML/CSS/JS highlighting, like the PHP mode for CodeMirror.
I just thought I would check with the SO hive mind before embarking on rolling my own CodeMirror mode. If I do make a new mode (and get anywhere with it) I'll post it here.
Thanks!
I made some tries to get a mixed mode with smarty and although my work is not perfect, so far it works well enough for me. I started from de htmlmixedmode to add a smarty mode :
CodeMirror.defineMode("smartymixed", function(config, parserConfig) {
var htmlMode = CodeMirror.getMode(config, {name: "xml", htmlMode: true});
var smartyMode = CodeMirror.getMode(config, "smarty");
var jsMode = CodeMirror.getMode(config, "javascript");
var cssMode = CodeMirror.getMode(config, "css");
function html(stream, state) {
var style = htmlMode.token(stream, state.htmlState);
if (style == "tag" && stream.current() == ">" && state.htmlState.context) {
if (/^script$/i.test(state.htmlState.context.tagName)) {
state.token = javascript;
state.localState = jsMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "javascript";
}
else if (/^style$/i.test(state.htmlState.context.tagName)) {
state.token = css;
state.localState = cssMode.startState(htmlMode.indent(state.htmlState, ""));
state.mode = "css";
}
}
return style;
}
function maybeBackup(stream, pat, style) {
var cur = stream.current();
var close = cur.search(pat);
if (close > -1) stream.backUp(cur.length - close);
return style;
}
function javascript(stream, state) {
if (stream.match(/^<\/\s*script\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*script\s*>/,
jsMode.token(stream, state.localState));
}
function css(stream, state) {
if (stream.match(/^<\/\s*style\s*>/i, false)) {
state.token = html;
state.localState = null;
state.mode = "html";
return html(stream, state);
}
return maybeBackup(stream, /<\/\s*style\s*>/,
cssMode.token(stream, state.localState));
}
function smarty(stream, state) {
style = smartyMode.token(stream, state.localState);
if ( state.localState.tokenize == null )
{ // back to anything from smarty
state.token = state.htmlState.tokens.pop();
state.mode = state.htmlState.modes.pop();
state.localState = state.htmlState.states.pop(); // state.htmlState;
}
return(style);
}
return {
startState: function() {
var state = htmlMode.startState();
state.modes = [];
state.tokens = [];
state.states = [];
return {token: html, localState: null, mode: "html", htmlState: state};
},
copyState: function(state) {
if (state.localState)
var local = CodeMirror.copyState(
( state.token == css ) ? cssMode : (( state.token == javascript ) ? jsMode : smartyMode ),
state.localState);
return {token: state.token, localState: local, mode: state.mode,
htmlState: CodeMirror.copyState(htmlMode, state.htmlState)};
},
token: function(stream, state) {
if ( stream.match(/^{[^ ]{1}/,false) )
{ // leaving anything to smarty
state.htmlState.states.push(state.localState);
state.htmlState.tokens.push(state.token);
state.htmlState.modes.push(state.mode);
state.token = smarty;
state.localState = smartyMode.startState();
state.mode = "smarty";
}
return state.token(stream, state);
},
compareStates: function(a, b) {
if (a.mode != b.mode) return false;
if (a.localState) return CodeMirror.Pass;
return htmlMode.compareStates(a.htmlState, b.htmlState);
},
electricChars: "/{}:"
}
}, "xml", "javascript", "css", "smarty");
CodeMirror.defineMIME("text/html", "smartymixed");
The switch to smarty mode is made in token function only but ...
You also have to patch the other basic modes ( css , javascript & xml ) to stop them on the { character so you can fall back in the token function to test it against a regexp ( { followed by a non blank character ).
This may help. I wrote a Smarty mode for CodeMirror2 this weekend. See:
http://www.benjaminkeen.com/misc/CodeMirror2/mode/smarty/
I've also forked the CodeMirror project with my change here:
https://github.com/benkeen/CodeMirror2
All the best -
Ben
[EDIT: this is now part of the main script. I'll be shortly adding a Smarty/HTML/CSS/JS mode].
The second part of the answer : a patch in benjamin smarty file to be able to leave it and fall back in smartymixedmode. So here is the patched verson of mode/smarty/smarty.js
CodeMirror.defineMode("smarty", function(config, parserConfig) {
var breakOnSmarty = ( config.mode == "smartymixed" ) ? true : false; // we are called in a "smartymixed" context
var keyFuncs = ["debug", "extends", "function", "include", "literal"];
var last;
var regs = {
operatorChars: /[+\-*&%=<>!?]/,
validIdentifier: /[a-zA-Z0-9\_]/,
stringChar: /[\'\"]/
}
var leftDelim = (typeof config.mode.leftDelimiter != 'undefined') ? config.mode.leftDelimiter : "{";
var rightDelim = (typeof config.mode.rightDelimiter != 'undefined') ? config.mode.rightDelimiter : "}";
function ret(style, lst) { last = lst; return style; }
function tokenizer(stream, state) {
function chain(parser) {
state.tokenize = parser;
return parser(stream, state);
}
if (stream.match(leftDelim, true)) {
if (stream.eat("*")) {
return chain(inBlock("comment", "*" + rightDelim));
}
else {
state.tokenize = inSmarty;
return ( breakOnSmarty == true ) ? "bracket" : "tag";
}
}
else {
// I'd like to do an eatWhile() here, but I can't get it to eat only up to the rightDelim string/char
stream.next();
return null;
}
}
function inSmarty(stream, state) {
if (stream.match(rightDelim, true)) {
state.tokenize = ( breakOnSmarty ) ? null : tokenizer;
return ( breakOnSmarty == true ) ? ret("bracket", null) : ret("tag", null);
}
var ch = stream.next();
if (ch == "$") {
stream.eatWhile(regs.validIdentifier);
return ret("variable-2", "variable");
}
else if (ch == ".") {
return ret("operator", "property");
}
else if (regs.stringChar.test(ch)) {
state.tokenize = inAttribute(ch);
return ret("string", "string");
}
else if (regs.operatorChars.test(ch)) {
stream.eatWhile(regs.operatorChars);
return ret("operator", "operator");
}
else if (ch == "[" || ch == "]") {
return ret("bracket", "bracket");
}
else if (/\d/.test(ch)) {
stream.eatWhile(/\d/);
return ret("number", "number");
}
else {
if (state.last == "variable") {
if (ch == "#") {
stream.eatWhile(regs.validIdentifier);
return ret("property", "property");
}
else if (ch == "|") {
stream.eatWhile(regs.validIdentifier);
return ret("qualifier", "modifier");
}
}
else if (state.last == "whitespace") {
stream.eatWhile(regs.validIdentifier);
return ret("attribute", "modifier");
}
else if (state.last == "property") {
stream.eatWhile(regs.validIdentifier);
return ret("property", null);
}
else if (/\s/.test(ch)) {
last = "whitespace";
return null;
}
var str = "";
if (ch != "/") {
str += ch;
}
var c = "";
while ((c = stream.eat(regs.validIdentifier))) {
str += c;
}
var i, j;
for (i=0, j=keyFuncs.length; i<j; i++) {
if (keyFuncs[i] == str) {
return ret("keyword", "keyword");
}
}
if (/\s/.test(ch)) {
return null;
}
return ret("tag", "tag");
}
}
function inAttribute(quote) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.next() == quote) {
state.tokenize = inSmarty;
break;
}
}
return "string";
};
}
function inBlock(style, terminator) {
return function(stream, state) {
while (!stream.eol()) {
if (stream.match(terminator)) {
state.tokenize = ( breakOnSmarty == true ) ? null : tokenizer;
break;
}
stream.next();
}
return style;
};
}
return {
startState: function() {
return { tokenize: tokenizer, mode: "smarty", last: null };
},
token: function(stream, state) {
var style = state.tokenize(stream, state);
state.last = last;
return style;
},
electricChars: ""
}
});
CodeMirror.defineMIME("text/x-smarty", "smarty");
The 1st line check if we are called by the smartymixed mode and tests are made on this contition, allowing smarty mode to run as before.