Getting next sibling with class in zepto - zepto

Normally this would not be a problem, but this time I don't control the dom. Anyway
<dt>foo</dt>
<dd>bar</dd>
<dd>bar
<div id="myElem">baz</div>
</dd>
<dd>bar</dd>
<dd>bar</dd>
<dd class="moo">bar</dd> <-- I want to get this one
<dd>bar</dd>
<dd>bar</dd>
<dd class="moo">bar</dd>
<dd>bar</dd>
<dd>bar</dd>
Here is what I have tried
$('#myElem').closest('dd').next('.moo').something();
$('#myElem').closest('dd').nextUntil('.moo').something();
However non of these get the next sibling with the class.
Anyone know how to traverse this?

Try
$('#myElem').closest('dd').nextAll('.moo:first').something();
or
$('#myElem').closest('dd').nextUntil('.moo').next().something();
I don't know zepto but from a quick check of the docs it doesn't look like you can do it with a simple chain. Try
var $dd = $('#myElem').closest('dd');
var $moo = $dd.next();
while (!$moo.is('.moo')){
$moo = $moo.next();
if ($moo.length == 0) break;
}
DEMO

You can accomplish by creating a simple new method:
/**
* Get the next element of a particular class, relative to a different element.
*/
var nextOfClass = function(relativeTo, className) {
var el = relativeTo, nextEl;
// Leading period will confuse Zepto.
if (className[0] === '.') className = className.slice(1);
while (el.next()) {
// If target element is found, stop
if (el.hasClass(className)) return el;
nextEl = el.next();
if (nextEl.length === 0) {
// No more siblings. Go up in DOM and restart loop to check parent
el = el.parent();
continue;
}
el = nextEl;
// End of doc. Give up.
if (el.parent().length === 0) return false;
}
};
Then you can simply use the following method:
var moo = nextOfClass($("#myElem"), '.moo');

Related

"CS0162: Warning as Error: Unreachable code detected" on Razor View

I have the following code within a script tag on my razor view:
self.regions = [];
#foreach(var region in Model.OperationRegions)
{
<text>
self.regions.push({
regionid: '#region.Region_Id',
regionname: '#region.Title',
selected: ko.observable(#(Model.RegionsList.Contains(region.Region_Id).ToString().ToLower()))
});
</text>
}
self.categories = [];
#foreach(var category in Model.Categories)
{
<text>
self.categories.push({
categoryid: '#category.Category_Id',
title: '#category.Title'
});
</text>
}
For clarity, the code outside of the foreach loops and within the text tags are Javascript and the purpose of the razor code is to populate my Javascript arrays with data from the server.
When I run this I am currently getting a server error saying "CS0162: Warning as Error: Unreachable code detected"
The error is thrown on the second "foreach" in the snippet.
Surprisingly I couldn't find another question referring to this error message on an MVC razor page so I'm posting this here.
My question is why is that line of code considered to be unreachable? I will update this question if I find anything else on my page to be relevant to the issue as I try to debug.
The error has disappeared now. I had renamed a property of my model and not recompiled before trying to load the page again. Recompiling made the error go away. I have no idea how the root cause translated to the error message shown but its fixed now in any case.
This is an extremely poor way to handle this. There's no need to build an array piece by piece like this. Just convert your list to JSON.
self.regions = #Html.Raw(Json.Encode(Model.OperationRegions.Select(region => new {
regionid = region.Region_Id,
regionname = region.Title,
selected = Model.RegionsList.Contains(region.Region_Id)
})));
The only thing this can't handle is making selected an observable. However, you can simply loop through the array and fix this:
for (var i = 0; i < self.regions.length; i++) {
self.regions[i].selected = ko.observable(self.regions[i].selected);
}
However, the better approach is to use another view model:
var OperationRegionViewModel = function (data) {
var self = {};
self.regionid = ko.observable(data.regionid);
self.regionname = ko.observable(data.regionname);
self.selected = ko.observable(data.selected);
return self;
};
Then, you can just do something like:
var regions = #Html.Raw(Json.Encode(Model.OperationRegions.Select(region => new {
regionid = region.Region_Id,
regionname = region.Title,
selected = Model.RegionsList.Contains(region.Region_Id)
})));
self.regions = $.map(regions, new OperationRegionViewModel);
Or, even better build your JSON all at once:
var json = #Html.Raw(Json.Encode(new {
regions = Model.OperationRegions.Select(r => new { ... }),
categories = Model.Categories.Select(c => new { ... }),
// etc
});
Then, inject this all into your view model:
var viewModel = (function (json) {
// other stuff
self.regions = $.map(json.regions, new OperationRegionViewModel);
self.categories = $.map(json.categories, new CategoryViewModel);
// etc
})(json);

can you re-initiate a ui-popover?

Since I'm injecting a <span ui-popover></span> after the DOM is constructed I need to reinitiate the popovers otherwise it won't show.
Is there away to do that?
HTML
<div ng-repeat="i in comments">
<div id={{i._id}} class="task" commentId={{i._id}}> {{i.text}} </div>
</div>
I'm using the external rangy library that injects 's around highlighted texts. You can also inject elementAttirbutes to accommodate these span, This is shown in this part of the code:
JS
function initHighLighter() {
var cssApplier = null;
highlighter = rangy.createHighlighter(document);
cssApplier = rangy.createClassApplier('highlight-a',{elementAttributes: {'uib-popover':"test"}}/*, {elementAttributes: {'data-toggle':"popover", 'data-placement':"bottom", 'title':"A for Awesome", 'data-selector':"true", 'data-content':"And here's some amazing content. It's very engaging. Right?"}}*/);
highlighter.addClassApplier(cssApplier);
cssApplier = rangy.createClassApplier('highlight-b', {elementAttributes: {'uib-popover':"test"}}/*, {elementAttributes: {'data-toggle':"popover", 'data-placement':"bottom", 'title':"B for Best", 'data-selector':"true", 'data-content':"And here's some amazing content. It's very engaging. Right?"}}*/);
highlighter.addClassApplier(cssApplier);
}
I'm calling on to highlight parts of the texts, only after I upload them from the server (highlighter1 calls on init highlight written above)
JS
(function(angular) {
'use strict';
angular.module('myApp', ['ui.bootstrap'])
.controller('Controller', function($scope, $http, $timeout) {
$http.get('/comments')
.success(function(response) {
$scope.comments = response;
var allEl=[];
var i;
for (i=0; i<response.length; i++) {
allEl.push(response[i]._id);
}
$http.post('/ranges', {"commentIds":allEl})
.success(function(result){
result.forEach(function(item){
highlighter1(item.dataAction, item.rangyObject, true);
})
})
});
})
})(window.angular);
So in the end my DOM is being changed AFTER I initiated everything and then the attributes associated with the span don't do anything.
your markup should be (notice the prefix)
<span uib-tooltip="hello world"></span>
or if you want dynamic content
$scope.welcomeMessage = "hello world"; // inside controller
..
<span uib-tooltip="{{welcomeMessage}}"></span>
if you want to reinitialize the tooltip, you can trigger a $destroy event and have it rebuilt, one way if by using ng-if and setting it to true when you need it.
<span ng-if="doneUpdating" uib-tooltip="hello world"></span>

Resetting and updating a list property in polymer 1.0.rc

I have a List of Map items property.
#property
List<Map<String, dynamic>> items = [
{'name': 'Acrid', 'checked': false},
{'name': 'Fishy', 'checked': false}
];
that are displayed when an if condition evaluates to true.
<template
restamp
is = "dom-if"
if = "[[normalChangedRestampedToggler]]">
<div
class = "layout horizontal wrap body auto"
id = "container">
<paper-material
pad-bottom
class = "dropdown-content layout vertical"
elevation = "5">
<paper-input
value = "{{filterValue}}"
label = "Search"
class = "margin">
<iron-icon
suffix
icon = "search"></iron-icon>
</paper-input>
<paper-menu multi
on-click = "onClickHandler">
<template
id = "repeat"
is = "dom-repeat"
items = "{{items}}"
filter = "{{filter(filterValue)}}">
<paper-item
role = "menuitemcheckbox"
toggles = "true"
active = "{{item.checked}}">
<paper-checkbox
checked = "[[item.checked]]"
checked-changed = "checkedChanged"></paper-checkbox>
[[label(item)]]
</paper-item>
</template>
</paper-menu>
</paper-material>
</div>
</template>
The conditional if is set by a paper-toggle button:
#reflectable
void toggleNormalChangedEvent( event, [_] ) {
normalChangedRestampedToggler = !normalChangedRestampedToggler;
set('normalChangedRestampedToggler', normalChangedRestampedToggler);
switch ( normalChangedRestampedToggler ) {
case false:
for(var item in data)
{
item['checked'] = false;
}
set('items', data);
data = new List<Map<String, dynamic>>( );
break;
}
}
While the UI toggles as expected, the
set('items', data);
does not seem to reset the items - the old checked state of the checkboxes still remain. It appears that the items property does not update.
Can this update be forced by some means?
PS Could the problem be due to the fact that the checkboxes are in a nested-template? If so, is there a way to overcome this problem?
Thanks
This is redundant
normalChangedRestampedToggler = !normalChangedRestampedToggler;
because this includes the above (added !)
set('normalChangedRestampedToggler',! normalChangedRestampedToggler);
for(var item in data) {
item['checked'] = false;
}
should be
for(int i = 0; i < items.length; i++) {
set('items.$i.checked', false);
}
data = new List<Map<String, dynamic>>( );
doesn't do anything because you probably assign the same list again and Polymer recognizes that there is no change.
Also template is="dom-repeat uses
items = "{{items}}"
why do you set data?

Accessing correct preference value in onsyncfrompreference after preference.reset()

I want to have a "Restore defaults" button in my extension's preferences window:
<prefpane id="nextplease.phrases" label="&options.phrases.title;"
image="chrome://nextplease/skin/Document.png">
<preferences>
<preference id="nextphrases" name="nextplease.nextphrase.expr0"
type="unichar"/>
...
</preferences>
<hbox flex="1">
...
<listbox id="nextphrases_list" seltype="multiple" flex="1"
onkeypress="nextplease.removeSelectedOnDelete(event, this);"
onselect="nextplease.enableDisableRemoveButton(this);"
preference="nextphrases" preference-editable="true"
onsynctopreference="return nextplease.syncListbox(this);"
onsyncfrompreference="nextplease.loadListboxPreference(this);"/>
...
<button label="&options.restoreDefault;"
oncommand="nextplease.restoreDefaultListbox(this);" />
</hbox>
</vbox>
</hbox>
</prefpane>
This is the JavaScript code for the functions:
nextplease.restoreDefaultListbox = function (node) {
var listbox = node.parentNode.parentNode.firstChild.selectedPanel;
var preferenceId = listbox.getAttribute("preference");
var preference = document.getElementById(preferenceId);
preference.reset();
alert("preference.value="+preference.value+"; preference.valueFromPreferences="+preference.valueFromPreferences);
preference.setElementValue(listbox);
};
nextplease.loadListboxPreference = function (listbox) {
var i, phrase, values;
var preferenceId = listbox.getAttribute("preference");
var preference = document.getElementById(preferenceId);
var prefValue = ??? // neither preference.value nor preference.valueFromPreferences work, see below
// alert("Loading: "+prefValue);
// remove all items already in listbox
for (i = listbox.itemCount; i >= 0; i--) {
listbox.removeItemAt(i);
}
values = prefValue.split("|");
for (i = 0; i < values.length; i++) {
phrase = values[i].replace(/&pipe;/g, "|");
if (phrase !== "") {
listbox.appendItem(phrase, phrase);
// Scroll to the newly added item to workaround
// what I think is a Firefox bug. I was getting
// Javascript exceptions when trying to read the
// values at the bottom that are "hidden".
listbox.ensureIndexIsVisible(listbox.getRowCount() - 1);
}
}
listbox.ensureIndexIsVisible(0);
};
Now, when I click on the button, the alert I get (after preference.reset()) looks like this:
preference.value=undefined; preference.valueFromPreferences=old user-set preference value
So it seems like in onsyncfrompreference the correct preference value can't be accessed except in a really ugly way:
if (preference.value === undefined) {
prefValue = preference.preferences.defaultBranch.getIntValue(preference.name); // or other type instead of Int
} else {
prefValue = preference.value;
}
Is there a better alternative? Specifically, one which would work for any preference type?
If pref.value === undefined, you should use pref.defaultValue to get the value to display. I updated the reference page and the onsyncfrompreference documentation to say that.

Changing the color of a selected link that is embedded in a table

I'm trying to use class names to change the color of a link after it has been selected, so that It will remain the new color, but only until another link is selected, and then it will change back.
I'm using this code that was posted by Martin Kool in this question:
<html>
<head>
<script>
document.onclick = function(evt) {
var el = window.event? event.srcElement : evt.target;
if (el && el.className == "unselected") {
el.className = "selected";
var siblings = el.parentNode.childNodes;
for (var i = 0, l = siblings.length; i < l; i++) {
var sib = siblings[i];
if (sib != el && sib.className == "selected")
sib.className = "unselected";
}
}
}
</script>
<style>
.selected { background: #f00; }
</style>
</head>
<body>
One
Two
Three
</body>
It works fine until I try to out the links in a table. Why is this? Be easy, I'm a beginner.
There is no error, the links are changing to the "selected" class, but when another link is selected, the old links are keeping the "selected" class instead of changing to "unselected". Basically, as far as I can tell, it's functioning like a vlink attribute, which is not what I'm going for.
And yes, the links are all in different cells, how would you suggest I change the code so that it works correctly?
OK, actually, I spoke too soon.
document.onclick = function(evt)
{
var el = window.event? event.srcElement : evt.target;
if (el && el.className == 'unselected')
{
var links = document.getElementsByTagName('a');
for (var i = links.length - 1; i >= 0; i--)
{
if (links[i].className == 'selected')
links[i].className = 'unselected';
}
el.className = 'selected';
}
return false;
}
This code you gave me works great, visually, it does exactly what I want it to do. However, It makes my links stop working... They change color, but dont link to anything, and then when I remove the script, they work fine. What am I doing wrong/what do I have to change to make this work?
Also, I want to do the same thing somewhere else in my website, where the links are all in one <div> tag, separated by <p> tags. How can I make this work?
You're looping through the siblings. If the links are in separate <td>'s then they're no longer siblings.
You can loop through all the links like this:
document.onclick = function(evt)
{
var el = window.event? event.srcElement : evt.target;
if (el && el.className == 'unselected')
{
var links = document.getElementsByTagName('a');
for (var i = links.length - 1; i >= 0; i--)
{
if (links[i].className == 'selected')
links[i].className = 'unselected';
}
el.className = 'selected';
}
return false;
}
I've also added a return false; at the end of the function to stop you going to '#'
Is there an error or is there just nothing happening? A good first step if you are a javascript beginner is to use a tool like Firebug so you see detailed error messages, and you can add in console.log statements to see what's going on while you run your code.
By ‘in tables’ do you mean putting each link in its own cell? Because that would make this line:
var siblings = el.parentNode.childNodes;
fail to select other links outside of the cell. You'd have to find another way to signal which element is the link container.

Resources