I'm using Firebase-util's intersection function to find all the comments for a given link. This seems to work fine the first time I call the join, but it doesn't seem to properly notify my value callback when I remove the contents of the database and replace them again. Shouldn't the references keep working as long as the resource path remains the same?
Run this example. When you click the button, it erases and recreates the data. As you can see, the comment list is not repopulated after the data gets recreated.
<link rel="import" href="https://www.polymer-project.org/components/polymer/polymer.html">
<script src="http://cdn.firebase.com/v0/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/firebase-util/0.1.0/firebase-util.min.js"></script>
<polymer-element name="my-element">
<template>
<h1>Test of Firebase-util.intersection</h1>
<div>
<button on-click={{initializeFirebase}}>Reset data</button>
</div>
<ul>
<template repeat="{{rootComment in comments}}">
<li>{{rootComment.comment.content}}
<ul>
<template repeat="{{subComment in rootComment.children}}">
<li>{{subComment.comment.content}}
<ul>
<template repeat="{{subSubComment in subComment.children}}">
<li>{{subSubComment.comment.content}}</li>
</template>
</ul>
</li>
</template>
</ul>
</li>
</template>
</ul>
</template>
<script>
Polymer('my-element', {
ready: function() {
var sanitizeUrl = function(url) {
return encodeURIComponent(url).replace(/\./g, '%ZZ');
};
var baseUrl = "https://nested-comments-test.firebaseio.com";
var linkUrl = baseUrl +
'/links/' +
sanitizeUrl(document.URL) +
'/comments';
var commentsUrl = baseUrl + '/comments';
var root = new Firebase(baseUrl);
this.initializeFirebase = function() {
function addLink(url, callback) {
var key = sanitizeUrl(url),
newLink = {
url: url,
createdAt: Firebase.ServerValue.TIMESTAMP
};
root.child('/links/' + key).update(newLink);
callback(key);
}
function addComment(attributes, callback) {
return root.child('/comments').push(attributes, callback);
}
function onCommentAdded(childSnapshot) {
var newCommentId = childSnapshot.name(),
attributes = {},
link = childSnapshot.val().link,
url = '/links/' + link + '/comments';
attributes[newCommentId] = true;
root.child(url).update(attributes);
}
root.remove(function() {
root.child('/comments').on('child_added', onCommentAdded);
addLink(document.URL, function(link) {
var attributes = {
link: link,
content: "This is the first comment."
},
firstCommentId, secondCommentId;
firstCommentId = addComment(attributes).name();
attributes = {
link: link,
content: "This is a reply to the first.",
replyToCommentId: firstCommentId
};
secondCommentId = addComment(attributes).name();
attributes = {
link: link,
content: "This is a reply to the second.",
replyToCommentId: secondCommentId
};
addComment(attributes);
attributes = {
link: link,
content: "This is another reply to the first.",
replyToCommentId: firstCommentId
};
addComment(attributes);
});
});
};
this.initializeFirebase();
var findChildrenForComment = function(snapshot, parentCommentId) {
var returnVal = [];
snapshot.forEach(function(snap) {
var comment = snap.val(),
commentId = snap.name();
if (comment.replyToCommentId === parentCommentId) {
var children = findChildrenForComment(snapshot, commentId);
var obj = {
commentId: commentId,
comment: comment,
parentId: parentCommentId
};
if (children.length) {
obj.children = children;
}
returnVal.push(obj);
}
});
return returnVal;
};
this.ref = Firebase.util.intersection(
new Firebase(linkUrl),
new Firebase(commentsUrl)
);
this.comments = {};
var that = this;
this.ref.on('value', function(snapshot) {
that.comments = findChildrenForComment(snapshot);
});
}
});
</script>
</polymer-element>
<my-element></my-element>
Apparently deleting a path entirely causes all callbacks on it to be canceled. The workaround for this behavior is to remove children one at a time rather than deleting their parent path.
Related
I'm trying to write a CRUD app. I'm having trouble figuring out how to edit and delete individual items. For each item created, I'm making two <a> tags inside of a <span> tag. One for edit and one for delete. But I can't seem to figure out how to make them do what they need to do. At this point they don't do anything because I can't figure out how to access the values correctly.
Note - I'm just beginning to learn jQuery so, any pro tips on that are appreciated.
Here's the html:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
</head>
<body>
<div class="container">
<form class='form'>
<input id="input" type="text" placeholder="Type here..">
</form>
<h3>Notes</h3>
<ul></ul>
<button id='clear'>Clear All</button>
</div>
<script src="app.js"></script>
</body>
</html>
And the javascript:
const app = {};
app.counter = (function(){
var i = -1;
return function(){
i += 1;
return i;
}
})()
app.create = function(element){
return document.createElement(element);
}
app.select = function(element){
return document.querySelector(element);
}
app.makeList = function(text) {
var i = app.counter();
var li = app.create('li');
var div = app.create('span');
var edit = app.create('a');
var del = app.create('a');
li.textContent = text;
edit.textContent = ' Edit';
edit.href = '#'
del.textContent = ' Delete';
del.href = '#'
div.appendChild(edit);
div.appendChild(del);
li.appendChild(div);
ul.insertBefore(li, ul.childNodes[0])
li.id = 'item' + i;
del.id = 'delete' + i;
edit.id = 'edit' + i;
}
// constants & variables
const ul = app.select('ul')
const input = app.select('input')
var notes;
$(document).ready(function(){
if (localStorage.getItem('notes')) {
notes = JSON.parse(localStorage.getItem('notes'));
} else {
notes = [];
}
localStorage.setItem('notes', JSON.stringify(notes));
// build list items and display them on the page
JSON.parse(localStorage.getItem('notes')).forEach(function(item){
app.makeList(item);
});
// when form is submitted
$('.form').submit(function(e){
e.preventDefault();
if (input.value.length > 0){
notes.push(input.value);
localStorage.setItem('notes', JSON.stringify(notes));
app.makeList(input.value);
input.value = "";
}
})
// clear items on page and from local storage
$('#clear').click(function(){
if (window.confirm('This will clear all items.\nAre you sure you want to do this?')){
localStorage.clear();
while (ul.firstChild) {
ul.removeChild(ul.firstChild)
}
}
});
$('ul').on('click', 'li', function(){
console.log(this.textContent) // logs whatever is typed + Edit Delete
})
});
Do something like this.
$("ul").on("click", "li", function(e) {
console.log(this.textContent); // logs whatever is typed + Edit Delete
if(e.target.id === "edit") {
//edit
}
if(e.target.id==="delete") {
//delete
}
});
You are trying to access elements before they are ready that is why you are not able to see anything.
Declare them on global level but assign them value after the document is ready.
var ul;
var input;
var notes;
$(document).ready(function () {
ul = app.select('ul')
input = app.select('input')
...Rest of your code
});
For the Edit and Delete Functionality
As you are appedning IDs in edit and delete button you need to parse that as well
$('ul').on('click', 'li', function (e) {
if (e.target.id.includes('edit')) {
console.log(` item ${e.target.id.split('edit')[1]} needs to be edited.`)
}
if (e.target.id.includes('delete')) {
//delete
}
})
I'm using this code right now, but I'm not really liking the way it looks.
function createPageSettingsPopup(page) {
return '<div class="form-group">' +
'<label for="page-title">Title</label>' +
'<input type="text" class="form-control" id="page-title" value="' + page.title + '">' +
'</div>'
}
Is there any alternative to write the HTML in strings to make it look better?
You could use template engines. This is at the expense of elements in the page, but the code will look much cleaner and the template easier to understand as HTML. Put the template in a script tag with type set to text/template
<script type="text/template" id="tmpl">
<div class="form-group">
<label for="page-title">Title</label>
<input type="text" class="form-control" id="page-title" value="{{pageTitle}}">
</div>
</script>
And modify your function as below. Remember to cache the template.
var template = document.getElementById('tmpl').innerHTML; // cache the HTML
function createPageSettingsPopup(page) {
// Find an replace the {{pageTitle}} with page.title, and then return the HTML string.
return template.replace(new RegExp('{{pageTitle}}', 'gi'), page.title)
}
var template = document.getElementById('tmpl').innerHTML; // cache the HTML
function createPageSettingsPopup(page) {
// Find an replace the {{pageTitle}} with page.title, and then return the HTML string.
return template.replace(new RegExp('{{pageTitle}}', 'gi'), page.title)
}
console.log(
createPageSettingsPopup({title:'Hello World'})
);
<script type="text/template" id="tmpl">
<div class="form-group">
<label for="page-title">Title</label>
<input type="text" class="form-control" id="page-title" value="{{pageTitle}}">
</div>
</script>
The above is a minimal example of a template engine, but there are great ones like mustache, handlebars.js, and pug.js
Assuming ES6 you can use backticks:
return `<div>
...
</div>`;
Or have a look at react, to manipulate your DOM, they use jsx which is really nice:
const element = <h1>Hello, world!</h1>;
In case you are using jQuery, sometimes you can do things like these:
var div = $('div').addClass('form-group');
div.append($('label').attr('for', 'page-title').text('Title');
...
Depending on your problem at hand it might also make sense to have the full html structure written up-front and then just manipulate some content and styling using js. In your example:
$('div#title').show();
$('div#title').find('label.page-title').text('Title');
You can try creating a HTML utility that creates elements, add necessary properties and the returns element.
I have created a small implementation of this utility in sample. Benefit of this is you can modify this utility to work with JSON based structure to create dynamic HTML.
Sample
function createPageSettingsPopup(page) {
var divParams = {
class: 'form-group'
}
var labelParams = {
for: 'page-title'
}
var inputParams = {
type: 'text',
class: "form-control",
id: 'page-title',
value: page.title
}
var div = utils.createMyElement('div', '', divParams);
var label = utils.createMyElement('label', 'Title', labelParams)
var input = utils.createMyElement('input', '', inputParams)
div.appendChild(label);
div.appendChild(input);
document.body.appendChild(div)
}
window.addEventListener('load', function() {
createPageSettingsPopup({
title: "foo"
})
})
// This code can be exported to another file
var utils = (function() {
function createMyElement(type, htmlString, params) {
var el = document.createElement(type);
if (htmlString)
el.innerHTML = htmlString;
addProps(el, params)
return el;
}
function addProps(el, props, key) {
if (Object.keys(props).length) {
for (var k in props) {
if (typeof(props[k]) === "object") {
addProps(el, props[k], k);
} else {
if (key) {
el[key][k] = props[k]
} else {
el[k] = props[k]
}
}
}
}
}
return {
createMyElement: createMyElement
}
})()
You can also try JSON based form.
Sample
JSFiddle
window.addEventListener('load', function() {
createPageSettingsPopup({
title: "foo"
})
})
function createPageSettingsPopup(page) {
var form = utils.createForm(getFormData(page))
document.body.appendChild(form)
}
// This can be stored in JSON file or in db and then can be fetched
function getFormData(page) {
var json = {
type: "div",
params: {
class: 'form-group',
innerHTML: "",
},
children: [{
type: 'label',
params: {
for: 'page-title',
innerHTML: "Title"
},
}, {
type: 'input',
params: {
type: 'text',
class: "form-control",
id: 'page-title',
value: page.title
}
}]
}
return json
}
// This is a generic utility and can be exported to a utility file
var utils = (function() {
function JSONBasedForm(form_json) {
var el = "";
if (form_json) {
el = createMyElement(form_json.type, form_json.params);
if (form_json.children && form_json.children.length > 0) {
form_json.children.forEach(function(child) {
var c_el = JSONBasedForm(child)
c_el && el.appendChild(c_el)
})
}
}
return el;
}
function createMyElement(type, params) {
var el = document.createElement(type);
addProps(el, params)
return el;
}
function addProps(el, props, key) {
if (Object.keys(props).length) {
for (var k in props) {
if (typeof(props[k]) === "object") {
addProps(el, props[k], k);
} else {
if (key) {
el[key][k] = props[k]
} else {
el[k] = props[k]
}
}
}
}
}
return {
createForm: JSONBasedForm
}
})()
This does not look better but is another way to create elements in JavaScript
Using the document.createElement you have more programmatic control over which attributes to set
function createPageSettingsPopup(page) {
var div = document.createElement("div");
div.className="form-group";
var label = document.createElement("label");
label.htmlFor="page-title";
label.textContent="Title";
var input = document.createElement("input");
input.type="text";
input.className="form-control";
input.id="page-title";
input.value=page.title;
label.appendChild(input);
div.appendChild(label);
return div;
}
Same in jQuery:
function createPageSettingsPopup(page) {
var $div = $("<div />",{"class":"form-group"});
$div.append(
$("<label />", {"htmlFor":"page-title").text("Title").append(
$("<input/>", { "type":"text","class":"form-control","id":"page-title"}).val(page.title)
)
);
return $div;
}
I've been trying to learn AngularJS recently, and hit a bump in the road with Localstorage i spend so many hours trying to make it save locally, I think that it's working as it should now, but now i would like to print out the data saved local from the JSON array, how can i go about that?
EDIT:
A bit of clarification, What im trying to achieve is getting the information i save in the localstorage out onto the website as a string, so it's readable. hope i'ts more understandable. Thanks in advance
My view.
<ion-list>
<div >
<ion-item ng-controller='ModalEditCtrl' ng-click="openModal()">
<div class="thumbnail" style="border:1px black solid">
</div>
<div ng-controller="createPerson" class="contactinfo" >
<li ng-repeat="contact in contactdetail.contactinfo"> {{contact.name}} </li>
</div>
</ion-item>
</div>
<div ng-controller="ModalAddCtrl">
<button type="button" ng-click="openModal()">+++</button>
</div>
</ion-list>
My controller
app.controller('createPerson', function ($scope) {
var id = id_counter = 1;
$scope.editorEnabled = false;
$scope.disableEditor = function() {
$scope.editorEnabled = false;
};
$scope.enableEditor = function() {
$scope.editorEnabled = true;
};
$scope.contactinfo = [
{name: 'test', phone: 1231, email: 'asd#asd.com'}
];
$scope.saveData = function () {
id_counter += 1;
$scope.editorEnabled = false;
$scope.contactinfo.push({
name: $scope.contactName,
phone: $scope.contactPhone,
email: $scope.contactEmail,
sort_id: id_counter
});
//$scope.todoText = ''; //clear the input after adding
localStorage.setItem('contactinfo', JSON.stringify($scope.contactinfo));
// localStorage.setItem("contacts", JSON.stringify(contacts));
}
$scope.loadData = function () {
var contacts = localStorage.getItem("contactinfo");
var contactdetail = JSON.parse(contacts); //
console.log(contactdetail);
}
$scope.clearData = function () {
window.localStorage.clear();
}
});
Your question is not very clear, I dont think you will be able to get much help unless you clean it up a little.
To print out the data (for debugging, usually) you could just add {{contactinfo|json}} somewhere in your html.
To actually display the data for use on the webpage the following should work for you.
<div ng-repeat="contact in contactinfo track by $index">
<div>Name: {{contact.name}}</div>
<div>Phone: {{contact.phone}}</div>
<div>Email: {{contact.email}}</div>
</div>
I think that some of that logic might be better split into a factory, too. Something like this maybe...?
var contactFactory = angular.module('contactFactory', []);
contactFactory.factory('contactInfo', ['$window', function ($window) {
var id = id_counter = 1;
var contacts = [];
function addContact(name, phone, email) {
id_counter += 1;
contacts.push({
name: name,
phone: phone,
email: email,
sort_id: id_counter
});
saveData();
}
function saveData(contactInfo) {
$window.localStorage.setItem('contactinfo', angular.fromJson(contacts));
}
function loadData() {
contacts = angular.toJson($window.localStorage.getItem('contactinfo'));
return contacts;
}
function clearData() {
$window.localStorage.removeItem('contactinfo');
}
return {
addContact: addContact,
saveData: saveData,
loadData: loadData,
clearData: clearData
};
}]);
var app = angular.module('yourAppName', ['contactFactory']);
app.controller('createPerson', ['$scope', 'contactInfo', function ($scope, contactInfo) {
$scope.editorEnabled = false;
$scope.disableEditor = function() {
$scope.editorEnabled = false;
};
$scope.enableEditor = function() {
$scope.editorEnabled = true;
};
$scope.contactinfo = [
{name: 'test', phone: 1231, email: 'asd#asd.com'}
];
$scope.saveData = function () {
contactInfo.addContact($scope.contactName, $scope.contactPhone, $scope.contactEmail);
$scope.editorEnabled = false;
}
$scope.loadData = contactInfo.loadData;
$scope.clearData = contactInfo.clearData;
}]);
Angular has wrapper for window, which should be used inside your code. There is also ngStorage module or many available solutions which are dealing with browser storage in Angular way. Moreover Angular has functions like angular.toJson() and angular.fromJson(). If e.g. jsonObj is JSON array then var obj = angular.fromJson(jsonObj) gives you JavaScript array. If jsonObj has array property inside then you should go with: var jsArray = angular.fromJson(jsonObj).array.
I am trying to use CSSTransitionGroup to animate Sentences in a SentenceList. When the "next" button is pressed I want the next Sentence to animate in and the 1st in the list to fadeout. However I get this error message:
Each child in an array should have a unique "key" prop. Check the
render method of Sentence.
I don't understand why that is since when I push Sentence into my List I am passing it a {sentence.id} as a "key" prop. Shouldn't React know that each sentence key is defined as such when rendering it?
I've tried defining the key again in the Sentence render method but to no avail. Are my State changes making React lose track of the current Sentence key?
Thanks for your help!
SentenceList:
var ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
var SentenceList = React.createClass({
getInitialState: function() {
return {
sentences: this.props.sentences
}
},
//receives sentence and new blip from Sentence
addBlip: function(sentence, value) {
//see where in the loaded sentences we are
var i = this.state.sentences.indexOf(sentence),
sentences = this.state.sentences,
// callback within a callback (post), the context changes inside the callback so we need to set this to self
self = this;
$.post(
'/sentences/' + sentence.id + '/blips',
{blip: {body: value}},
//set sentence we blipped into as answered
//reset state to reload sentences state after post
function(response) {
sentences[i].answered = true;
// sentences[i].statistics = response.statistics;
// put dummy content first then work it out in the backend to receive the format you want to receive (better to work from front to back)
sentences[i].statistics = [
{word: "butts", frequency: "95%"},
{word: "dogs", frequency: "2%"},
{word: "vegetables", frequency: "1%"},
{word: "sun", frequency: "2%"}
];
self.setState({sentences: sentences});
});
},
//take in a sentence (sent from Sentence) and find current position in loaded sentences and set it to dismissed, then reload list
dismissSentence: function(sentence) {
var i = this.state.sentences.indexOf(sentence),
sentences = this.state.sentences;
sentences[i].dismissed = true;
this.setState({sentences: sentences});
},
//list undismissed sentences and take out the first 3 for display
topThreeRemainingSentences: function() {
var unanswered = _.where(this.state.sentences, {dismissed: false});
return unanswered.slice(0, 3);
},
render: function() {
var remaining = this.topThreeRemainingSentences(),
sentences = [],
index = 0;
//loop through sentences until we have 3 remaining sentences loaded
while (index <= (remaining.length - 1)) {
var sentence = remaining[index];
sentences.push(
<Sentence key={sentence.id}
isActive={index == 0}
isNext={index == 1}
isNnext={index == 2}
onDismiss={this.dismissSentence}
onSubmitBlip={this.addBlip}
details={sentence} />
)
index = index + 1;
}
return (
<ReactCSSTransitionGroup transitionName="animate">
<div>{sentences}</div>
</ReactCSSTransitionGroup>
)
}
});
Sentence:
var Sentence = React.createClass({
getDefaultProps: function() {
return {
onSubmitBlip: function() { console.log(arguments) }
}
},
//pass sentence and new blip to submit function
addBlip: function(e) {
e.preventDefault();
var blipBody = this.refs.newBlip.getDOMNode().value
this.props.onSubmitBlip(this.props.details, blipBody);
},
//send sentence to List to set it to dismissed
dismissSentence: function(e) {
e.preventDefault();
this.props.onDismiss(this.props.details);
},
render: function() {
var phrase = this.props.details.body,
phrase_display = phrase.split("*"),
before = phrase_display[0],
after = phrase_display[1],
positionClass,
stats;
if (this.props.isActive) {
positionClass = "active-sentence"
} else if (this.props.isNext) {
positionClass = "next-sentence"
} else if (this.props.isNnext) {
positionClass = "nnext-sentence"
}
//find stats for sentence if answered from json and push them into array ["word", x%]
if (this.props.details.answered) {
var words = [];
this.props.details.statistics.forEach(function(statistic) {
words.push(<li className="stats-list"><span className="stats-list-word">{statistic.word} </span>
<span className="stats-list-percent">{statistic.frequency} </span> </li>)
})
stats = <div><span className="stats-list-header">others said:</span> {words}</div>
}
if (this.props.isActive) {
nextButton = <div className="next-button" onClick={this.dismissSentence}>V</div>
}
if (this.props.isNext) {
nextButton = <div></div>
}
if (this.props.isNnext) {
nextButton = <div></div>
}
return (
<div className={"blipForm " + positionClass}>
{before}
<form onSubmit={this.addBlip}>
<input type="text"
ref="newBlip" />
</form>
{after}
{nextButton}
<br/>
<ul>{stats}</ul>
</div>
)
}
});
The <li> elements created in the Sentence component's render method need key attributes:
this.props.details.statistics.forEach(function(statistic) {
words.push(<li className="stats-list" key={statistic.id}>...</li>);
});
I want to use a select x-editable in my Meteor application. My goal is to assign users to groups. This should be reactive, so when you assign a user, other clients should see the changes. The current problem is that the assignment works (data-value changes), but only the user who made the change is able to see the new value.
Here is my code:
Template.userGroup.rendered = function() {
var groupId = this.data._id;
var sourceUsers = [];
Users.find().forEach(function(user) {
sourceUsers.push({value: user._id, text: user.username});
});
Tracker.autorun(function() {
$('.assign-user').editable("destroy").editable({
emptytext: "Empty",
source: sourceUsers,
success: function(response, result) {
if (result) {
Groups.update({_id: groupId}, {$set: {adminId: result}});
}
}
});
});
};
<template name="userGroup">
</template>
I already tried to "destroy" the stale x-editable and put it inside the Tracker.autorun function, but unfortunately, this does not work.
Any help would be greatly appreciated.
I don't use Tracker.autorun but I use x-editable for inline editing like this:
(also used it for group assigments - just like your case, but found it too clumsy on the UI side). Anyway, here's my code:
Template
<template name="profileName">
<td valign='top'>
<div id="profileNameID" class="editable" data-type="text" data-rows="1">{{profile.name}}</div>
</td>
</template>
And on the JS side
Template.profileName.rendered = function () {
var Users = Meteor.users;
var container, grabValue, editableColumns, mongoID,
_this = this;
var container = this.$('#profileNameID');
var editableColumns = container.size();
grabValue = function () {
var gValue = $.trim(container.html());
return gValue;
};
$.fn.editable.defaults.mode = 'inline';
return container.editable({
emptytext: 'Your name goes here',
success: function (response, newValue) {
var mongoID = removeInvisibleChars($(this).closest("tr").find(".mongoid").text());
var editedUser = _users.findOne({
_id: mongoID
});
Meteor.users.update(mongoID, {
$set: {
"profile.name": newValue
}
});
return container.data('editableContainer').formOptions.value = grabValue;
}
});
Update happens immediately on all subscribed authorized clients.