I have a note taking application that im working on . Its made to look like Google Keep
It saves each note in local storage .
I would like to add a delete option to each of the notes (similar to Keep) , but don't know know to do it.
The full code is on my Github page https://github.com/oxhey/keep
HTML :
<!doctype html>
<html>
<head>
<title>Notes</title>
<link href="styles/normalize.css" rel="stylesheet" />
<link href="styles/main.css" rel="stylesheet" />
<link rel="stylesheet" type="text/css" href="window.css">
</head>
<script>
var nw = require('nw.gui');
var win = nw.Window.get();
win.isMaximized = false;
</script>
<body id="gui">
<header class="app-header">
<ul style="margin-top:0.3px">
<li><a href='#' title='Close' id='windowControlClose'></a></li>
<li><a href='#' title='Maximize' id='windowControlMaximize'></a></li>
<li><a href='#' title='Minimize' id='windowControlMinimize'></a></li>
</ul>
<h2 style="margin-top: 10px;margin-left: 20px;color: #fff;">Notes</h2>
</header>
<section class="main-section">
<article class="note add-note">
<h1 class="note-title"></h1>
<p class="note-content">Add a note</p>
</article>
</section>
<script src="js/jquery.js"></script>
<script src="js/main.js"></script>
<script>
// Min
document.getElementById('windowControlMinimize').onclick = function()
{
win.minimize();
};
// Close
document.getElementById('windowControlClose').onclick = function()
{
win.close();
gui.App.closeAllWindows();
};
// Max
document.getElementById('windowControlMaximize').onclick = function()
{
if (win.isMaximized)
win.unmaximize();
else
win.maximize();
};
win.on('maximize', function(){
win.isMaximized = true;
});
win.on('unmaximize', function(){
win.isMaximized = false;
});
</script>
</body>
</html>
Javascript : Main.js
var Strings = {
'en-us': {
DEFAULT_TITLE: "Title",
ADD_NOTE: "Add a note",
SEARCH_PLACEHOLDER: "Search Jin's Keep"
}
};
var Lang = Strings['en-us'];
var Keys = {
ENTER: 10
}
var notes;
function Note(title, content, id) {
Note.numInstances = (Note.numInstances || 0) + 1;
this.id = id ? id : Note.numInstances
this.title = title;
this.content = content;
}
Note.prototype.render = function(index) {
var elem = $('<article class="note" data-index="' + index + '"><h1 class="note-title">' + this.title + '</h1><p class="note-content">' + this.content + '</p></article>');
$(".add-note").after(elem);
}
Note.prototype.toJSON = function() {
return {
id: this.id,
title: this.title,
content: this.content
};
}
function createNote() {
var elem = $(".add-note");
var title = elem.find(".note-title");
var content = elem.find(".note-content");
elem.removeClass("active");
title.hide();
if(title.text() != Lang.DEFAULT_TITLE || content.text() != Lang.ADD_NOTE) {
var id = notes ? notes.length+1 : 1;
var note = new Note(title.text(), content.text(), id);
notes.push(note);
note.render(notes.length-1);
title.text(Lang.DEFAULT_TITLE);
content.text(Lang.ADD_NOTE);
saveNotes();
}
}
function activateNote(note) {
var title = note.find(".note-title");
note.addClass("active");
title.show();
if(isEmpty(title.text())) {
title.text(Lang.DEFAULT_TITLE);
}
}
function saveCurrentNote() {
var noteElement = $(".note.active");
if(noteElement) {
console.log("will save this element: ", noteElement[0]);
var noteIndex = noteElement.attr("data-index");
var note = notes[noteIndex];
note.title = noteElement.find(".note-title").text();
note.content = noteElement.find(".note-content").text();
saveNotes();
deactivateCurrentNote(noteElement);
}
}
function deactivateCurrentNote(note) {
note.removeClass("active");
var title = note.find(".note-title");
if(isEmpty(title.text()) || title.text() == Lang.DEFAULT_TITLE) {
title.hide();
}
$(":focus").blur();
}
function isEmpty(string) {
return string.replace(/\s| /g, '').length == 0;
}
function addTabIndex() {
tabIndex = 3;
$(".note .note-content, .note .note-title").each(function() {
var el = $(this);
el.attr("tabindex", tabIndex++);
});
}
function loadNotes() {
var rawObjects = JSON.parse(localStorage.getItem("notes"));
if(rawObjects && rawObjects.length) {
rawObjects.forEach(function(obj, index) {
obj.__proto__ = Note.prototype;
obj.render(index);
});
return rawObjects;
} else {
console.warn("Couldn't load any note because local storage is empty.");
return [];
}
}
function saveNotes() {
localStorage.setItem("notes", JSON.stringify(notes))
}
$(document).ready(function() {
notes = loadNotes();
addTabIndex();
$(".note").each(function() {
var note = $(this);
var title = note.find(".note-title");
if(isEmpty(title.text()) || title.text() == Lang.DEFAULT_TITLE ) {
title.hide();
}
});
$(".main-section").on("click", ".note .note-content, .note .note-title", function(evt) {
var note = $(this).parent();
activateNote(note);
//console.log('2 - Setting content editable to true', evt);
var noteSection = $(this);
noteSection.prop("contentEditable", true);
});
$(".main-section").on("click", ".note .note-title", function(evt) {
//console.log("3 - Clearing TITLE's text");
var title = $(this);
if(title.text() == Lang.DEFAULT_TITLE) {
title.text("");
}
});
$(".main-section").on("click", ".note .note-content", function(evt) {
//console.log('4 - Clearing CONTENT text', evt);
var content = $(this);
if(content.text() == Lang.ADD_NOTE) {
content.text("");
content.focus();
}
});
$(".main-section").on("click", function(evt) {
if(evt.target == this) {
//console.log('5', evt);
var currentNote = $(".note.active");
if(currentNote.length) {
//console.log('5.a');
if(currentNote.hasClass("add-note")) {
console.log('5 - Creating a new note');
createNote();
} else {
console.log('5 - Saving current note');
saveCurrentNote();
}
if(currentNote.find(".note-title").text() == Lang.DEFAULT_TITLE) {
currentNote.find(".note-title").hide();
}
}
}
});
$(".main-section").on("keypress", ".note .note-content, .note .note-title", function(evt) {
var currentNote = $(".note.active");
console.log('6');
if(evt.which == Keys.ENTER && evt.ctrlKey) {
console.log('2');
if(currentNote.hasClass("add-note")) {
createNote();
} else {
saveCurrentNote();
}
}
});
});
Thanks
Something like this? Use a data attribute on a delete button and pass that as a parameter to a removeNote function.
HTML
<button class="delete" data-id="1">Delete note</button>
JS
$(document).on('click', '.delete', function () {
var id = $(this).data('id');
removeNote(id);
});
function removeNote(id) {
localStorage.removeItem(id);
}
Related
I have a dynamically generating search listing page on hubspot based on search term queries.
The HTML structure is as follows:
<div class="search-listing">
a class="hs-search-results__title" href="www.something.com"><img src="Some image"></a>
<a class="more-link">Read More</a>
</div>
Now the page has a few of these .search-listing divs. I want the href of the more-link inside each .search-listing take the value of the href in the hs-search-results__title link of each search-listing.
Addition:
This is the code that is used to give the value to the hs-search-results__title in the first place. Maybe this would be of help?
HTML:
<div class="hs-search-results2">
<template class="hs-search-results__template">
<li>
<div class="hs-search-results__featured-image">
<img src="">
</div>
Content Title
<p class="hs-search-results__description">Description</p>
<p>
<a class="more-link" href="#" style="color: #FFF!important">Read More</a>
</p>
</li>
</template>
<ul id="hsresults" class="hs-search-results__listing"></ul>
<div class="hs-search-results__pagination" data-search-path="{{ site_settings.content_search_results_page_path }}">
</div>
</div>
jQuery:
var hsResultsPage = function(_resultsClass) {
function buildResultsPage(_instance) {
var resultTemplate = _instance.querySelector('.hs-search-results__template'),
resultsSection = _instance.querySelector('.hs-search-results__listing'),
searchPath = _instance.querySelector('.hs-search-results__pagination').getAttribute('data-search-path'),
prevLink = _instance.querySelector('.hs-search-results__prev-page'),
nextLink = _instance.querySelector('.hs-search-results__next-page');
var searchParams = new URLSearchParams(window.location.search.slice(1));
function getTerm() {
return searchParams.get('term') || "";
}
function getOffset() {
return parseInt(searchParams.get('offset')) || 0;
}
function getLimit() {
return parseInt(searchParams.get('limit'));
}
function addResult(title, url, description, featuredImage) {
var newResult = document.importNode(resultTemplate.content, true);
function isFeaturedImageEnabled() {
if (newResult.querySelector('.hs-search-results__featured-image img')) {
return true;
}
}
newResult.querySelector('.hs-search-results__title').innerHTML = title;
newResult.querySelector('.hs-search-results__title').href = url;
newResult.querySelector('.hs-search-results__featured-image a').href = url;
newResult.querySelector('.hs-search-results__description').innerHTML = description;
if (typeof featuredImage !== 'undefined' && isFeaturedImageEnabled()) {
newResult.querySelector('.hs-search-results__featured-image img').src = featuredImage;
}
resultsSection.appendChild(newResult);
}
function fillResults(results) {
results.results.forEach(function(result, i){
addResult(result.title, result.url, result.description, result.featuredImageUrl);
});
}
function emptyPagination() {
prevLink.innerHTML = "";
nextLink.innerHTML = "";
}
function emptyResults(searchedTerm) {
resultsSection.innerHTML = "<div class=\"hs-search__no-results\"><p>Sorry. There are no results for \"" + searchedTerm + "\"</p>" +
"<p>Try rewording your query, or browse through our site.</p></div>";
}
function setSearchBarDefault(searchedTerm) {
var searchBars = document.querySelectorAll('.hs-search-field__input');
Array.prototype.forEach.call(searchBars, function(el){
el.value = searchedTerm;
});
}
function httpRequest(term, offset) {
var SEARCH_URL = "/_hcms/search?",
requestUrl = SEARCH_URL + searchParams + "&analytics=true",
request = new XMLHttpRequest();
request.open('GET', requestUrl, true);
request.onload = function() {
if (request.status >= 200 && request.status < 400) {
var data = JSON.parse(request.responseText);
setSearchBarDefault(data.searchTerm);
if (data.total > 0) {
fillResults(data);
paginate(data);
}
else {
emptyResults(data.searchTerm);
emptyPagination();
}
}
else {
console.error('Server reached, error retrieving results.');
}
};
request.onerror = function() {
console.error('Could not reach the server.');
};
request.send();
}
function paginate(results) {
var updatedLimit = getLimit() || results.limit;
function hasPreviousPage() {
return results.page > 0;
}
function hasNextPage() {
return results.offset <= (results.total - updatedLimit);
}
if (hasPreviousPage()) {
var prevParams = new URLSearchParams(searchParams.toString());
prevParams.set('offset', (results.page * updatedLimit) - parseInt(updatedLimit));
prevLink.href = "/" + searchPath + "?" + prevParams;
prevLink.innerHTML = "< Previous page";
}
else {
prevLink.parentNode.removeChild(prevLink);
}
if (hasNextPage()) {
var nextParams = new URLSearchParams(searchParams.toString());
nextParams.set('offset', (results.page * updatedLimit) + parseInt(updatedLimit));
nextLink.href = "/" + searchPath + "?" + nextParams;
nextLink.innerHTML = "Next page >";
}
else {
nextLink.parentNode.removeChild(nextLink);
}
}
var getResults = (function() {
if (getTerm()) {
httpRequest(getTerm(), getOffset());
}
else {
emptyPagination();
}
})();
}
(function() {
var searchResults = document.querySelectorAll(_resultsClass);
Array.prototype.forEach.call(searchResults, function(el){
buildResultsPage(el);
});
})();
}
if (document.attachEvent ? document.readyState === "complete" : document.readyState !== "loading"){
var resultsPages = hsResultsPage('.hs-search-results2');
}
else {
document.addEventListener('DOMContentLoaded', function() {
var resultsPages = hsResultsPage('.hs-search-results2');
});
}
You can loop through all the search-listing and find the corresponding element inside of the element to get/set the attribute.
Try the following way:
$('.search-listing').each(function(){
var searchHref = $(this).find('.hs-search-results__title').attr('href');
$(this).find('.more-link').attr('href', searchHref)
});
a img{
width:25px;
height: auto;
border: 1px solid gray;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="search-listing">
<a class="hs-search-results__title" href="https://stackoverflow.com/"><img src="https://cdn.sstatic.net/Sites/stackoverflow/company/img/logos/so/so-icon.png?v=c78bd457575a"></a>
<a class="more-link">Read More</a>
</div>
<div class="search-listing">
<a class="hs-search-results__title" href="https://google.com/"><img src="https://cdn4.iconfinder.com/data/icons/new-google-logo-2015/400/new-google-favicon-512.png"></a>
<a class="more-link">Read More</a>
</div>
I am working on a chrome extension where keywords are populated in search from MYSQL database
my popup.js :
// UI creation
!function() {
"use strict";
/*|================================================================|*/
/*| UI creation |*/
/*|================================================================|*/
var initSectionByBgColorFromTemplate = function (sectionNodeTemplate, bgColorCode, wordGroupsDict) {
var sectionNode = sectionNodeTemplate.cloneNode(true);
var handlingIndex = 0;
var classNames = ["pure-toggle-checkbox", "pure-toggle", "color-header", "remove-group-button", "highlight-words"];
var toggleCheckbox = sectionNode.getElementsByClassName(classNames[handlingIndex])[0];
toggleCheckbox.id = classNames[handlingIndex].concat("-", bgColorCode);
toggleCheckbox.dataset.bgColor = bgColorCode;
toggleCheckbox.checked = wordGroupsDict[bgColorCode].isOn;
toggleCheckbox.addEventListener("change", wordGroupToogleHandlerFactory(wordGroupsDict));
handlingIndex++;
var toggleLabel = sectionNode.getElementsByClassName(classNames[handlingIndex])[0];
toggleLabel.id = classNames[handlingIndex].concat("-", bgColorCode);
toggleLabel.htmlFor = toggleCheckbox.id;
handlingIndex++;
var header = sectionNode.getElementsByClassName(classNames[handlingIndex])[0];
header.style.backgroundColor = "#".concat(bgColorCode);
header.textContent = bgColorCode;
handlingIndex++;
var removeButton = sectionNode.getElementsByClassName(classNames[handlingIndex])[0];
removeButton.dataset.bgColorCode = bgColorCode;
removeButton.addEventListener("click", removeGroupHandlerFactory(wordGroupsDict, sectionNode));
handlingIndex++;
var textarea = sectionNode.getElementsByClassName(classNames[handlingIndex])[0];
textarea.id = classNames[handlingIndex].concat("-", bgColorCode);
textarea.dataset.bgColor = bgColorCode;
textarea.value = wordGroupsDict[bgColorCode].words.join(" ");
textarea.addEventListener("blur", wordListChangeHandlerFactory(wordGroupsDict));
handlingIndex++;
return sectionNode;
};
var mainBlock = document.getElementById("mainBlock");
var sessionTemplate = mainBlock.getElementsByTagName("section")[0];
var newGroupForm = document.getElementById("new-group-form");
var colorInputBox = document.getElementById("new-group-color");
/*|================================================================|*/
/*| load UI data and event binding |*/
/*|================================================================|*/
var getDefaultWordGroup = function (groupName) {
return {
groupName: groupName,
isOn: false,
words: []
};
};
var createNewGroupInDict = function (wordGroupsDict, groupName) {
var wordGroup = wordGroupsDict[groupName];
if (!wordGroup) {
wordGroup = getDefaultWordGroup(groupName);
wordGroupsDict[groupName] = wordGroup;
}
};
var saveAndSendMsg = function (wordGroupsDict) {
chrome.storage.sync.set({
wordGroupsDict: wordGroupsDict
}, function () {
// console.log("wordGroupsDict saved");
});
chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
var messageBody = wordGroupsDict;
chrome.tabs.sendMessage(tabs[0].id, messageBody, function(response) {
// console.log(response.content);
});
});
};
var wordGroupToogleHandlerFactory = function (wordGroupsDict) {
return function (event) {
var groupName = event.target.dataset.bgColor;
var wordGroup = wordGroupsDict[groupName];
wordGroup.isOn = event.target.checked;
saveAndSendMsg(wordGroupsDict);
};
};
var wordListChangeHandlerFactory = function (wordGroupsDict) {
return function (event) {
var groupName = event.target.dataset.bgColor;
var wordGroup = wordGroupsDict[groupName];
wordGroup.words = event.target.value.match(/[^\s]+/g) || [];
saveAndSendMsg(wordGroupsDict);
};
};
var removeGroupHandlerFactory = function (wordGroupsDict, sectionNode) {
return function (event) {
sectionNode.parentElement.removeChild(sectionNode);
delete wordGroupsDict[event.target.dataset.bgColorCode];
saveAndSendMsg(wordGroupsDict);
};
};
/*|================================================================|*/
/*| load extension settings |*/
/*|================================================================|*/
chrome.storage.sync.get('wordGroupsDict', function (wordGroupsDict) {
// I just dont know how chrome.storage.sync works...
// + nothing inside, return {}
// + find the key, return {key: value}
wordGroupsDict = wordGroupsDict.wordGroupsDict || wordGroupsDict;
/*|================================================================|*/
/*| popup UI and event binding |*/
/*|================================================================|*/
// use default for 1st time
var colorGroups = Object.keys(wordGroupsDict);
if (colorGroups.length === 0) {
colorGroups = ["C72E04", "FA9507", "CACF44", "27AB99"].slice(1);
colorGroups.forEach( colorGroup => createNewGroupInDict(wordGroupsDict, colorGroup) );
}
/*
if (colorGroups.length === 0) {
var valNew = [];
var array = [];
$.ajax({
type: 'post',
url: 'http://localhost/chrome-highlight-extension-master/js/loadcolor.php',
data: {
},
success: function (response) {
var res = $.trim(response);
valNew = res.split(',');
for(var i=0;i<(valNew.length);i++){
colorGroups.push(valNew[i]);
}
colorGroups.forEach( colorGroup => createNewGroupInDict(wordGroupsDict, colorGroup) );
}
});
} */
// remove template and append initialized sections
mainBlock.removeChild(sessionTemplate);
colorGroups.forEach(function (bgc) {
mainBlock.appendChild(initSectionByBgColorFromTemplate(sessionTemplate, bgc, wordGroupsDict));
});
newGroupForm.addEventListener("submit", function (event) {
event.preventDefault();
if (colorInputBox.value && colorInputBox.value.length > 0 && colorInputBox.checkValidity()) {
console.log("submit OK");
createNewGroupInDict(wordGroupsDict, colorInputBox.value);
mainBlock.appendChild(initSectionByBgColorFromTemplate(sessionTemplate, colorInputBox.value, wordGroupsDict));
}
console.log("submit");
});
colorInputBox.addEventListener("onload", function (event) {
if (event.target.checkValidity()) {
event.target.style.backgroundColor = "#".concat(event.target.value);
} else {
event.target.style.backgroundColor = "white";
}
});
});
}();
In the above code I have some static colors
colorGroups = ["C72E04", "FA9507", "CACF44", "27AB99"];
But I want these colors from MYSQL db , so i used ajax to get colors (that part of the code has been commented )
if (colorGroups.length === 0) {
var valNew = [];
var array = [];
$.ajax({
type: 'post',
url: 'http://localhost/chrome-highlight-extension-master/js/loadcolor.php',
data: {
},
success: function (response) {
var res = $.trim(response);
valNew = res.split(',');
for(var i=0;i<(valNew.length);i++){
colorGroups.push(valNew[i]);
}
colorGroups.forEach( colorGroup => createNewGroupInDict(wordGroupsDict, colorGroup) );
}
});
}
In the ajax code i am able to fetch colors from my table but the problem is with createNewGroupInDict
this is the error I am facing
Error in response to storage.get: TypeError: Cannot read property 'addEventListener' of null
at Object.callback (chrome-extension://pgommpfcheidcagdchjjdhffidaefhaf/js/popup.js:159:16)
at chrome-extension://pgommpfcheidcagdchjjdhffidaefhaf/js/popup.js:113:22
at chrome-extension://pgommpfcheidcagdchjjdhffidaefhaf/js/popup.js:176:2
popup.html:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Keyword Page</title>
<link href="css/popup.css" media="screen" rel="stylesheet" type="text/css">
<link href="css/toggle.css" media="screen" rel="stylesheet" type="text/css">
</head>
<body>
<main id="mainBlock">
<div class="button-block">
<form id="new-group-form">
<input id="new-group-color" type="text" pattern="[0-9a-fA-f]{6}" title="hex color code" />
<input id="add-button" class="button" type="submit" value="+" title="add group" />
</form>
</div>
<section>
<span class="toggle">
<input type="checkbox" class="pure-toggle-checkbox" hidden />
<label class="pure-toggle flip" for="pure-toggle-">
<div class="fontawesome-remove">OFF</div>
<div class="fontawesome-ok">ON</div>
</label>
</span>
<header class="color-header">C72E04</header>
<button class="remove-group-button button">x</button>
<textarea class="highlight-words" placeholder="(separated by a space)" > </textarea>
</section>
</main>
<script src="js/popup.js"></script>
<script src="js/jquery.min.js"></script>
<script src="js/custom.js"></script>
</body>
</html>
hi this is my code snippet:
self.arrow = function () {
alert("button clicked");
var active_el = $(this);
$('.movie-listing-header').each(function () {
if ($(this).get(0) === active_el.parent().get(0)) {
if ($(this).hasClass('active')) {
$(this).siblings('.showtimes').hide();
} else {
$(this).siblings('.showtimes').show();
}
$(this).toggleClass('active');
} else {
$(this).removeClass('active');
$(this).siblings('.showtimes').hide();
}
});
}
it is part of my viewmodel, and the alert("button clicked") works, but the rest of the code does not ... Here is the button click code:
<a class="icon arrow" data-bind="click: $parent.arrow"></a>
my question is HOW do I get the code after the alert to function.
this is the entire js, containing the the View Model
function TheatreViewModel(theatre) {
var self = this,
initialData = theatre || Regal.userPrimaryTheatre || {},
theatreServiceParams = {
tmsId: initialData.TmsId,
date: initialData.selectedDate || new Date()
};
self.TheatreName = initialData.TheatreName || '';
self.PhoneNumber = initialData.PhoneNumber || '';
self.selectedTheatreTms = initialData.TmsId;
self.theatre = ko.observable();
self.isLoading = ko.observable(false);
self.selectedDate = ko.observable(initialData.selectedDate || new Date());
self.filterFormats = [];
self.selectedFormat = ko.observable(Regal.allFormatsFilterItem);
self.filterFormats.push(Regal.allFormatsFilterItem);
if (Regal.movieFormats) {
var filtered = _.where(Regal.movieFormats, {
Filterable: true
});
_.forEach(filtered, function(f) {
f.enabled = ko.observable(false);
self.filterFormats.push(f);
});
}
self.addressText = ko.computed(function() {
var theat = ko.unwrap(self.theatre);
var addie;
if (!theat || theat && !theat.Address1) {
addie = initialData;
} else {
addie = theat;
}
return addie.Address1 + ', ' + addie.City + ' ' + addie.State + ' ' + addie.PostalCode;
});
self.mapEmbedUrl = ko.computed(function() {
var a = self.addressText();
return 'http://maps.google.com/maps?q=' + encodeURI(a);
});
self.movies = ko.computed(function() {
var thea = self.theatre(),
mov = ko.unwrap((thea || {}).Movies),
format = self.selectedFormat();
if (format && format !== Regal.allFormatsFilterItem) {
return _.filter(mov, function(m) {
return _.contains(_.pluck(m.formats(), 'Id'), format.Id);
});
}
return mov;
});
self.getPerformances = function() {
self.isLoading(true);
Regal.loadTheatre(self.selectedDate(), self.selectedTheatreTms,
function(data) {
self.isLoading(false);
if (data) {
var allFmts = _.uniq(_.flatten(_.map(ko.unwrap(data.Movies), function(mov) {
return mov.formats();
})));
_.forEach(allFmts, function(fmt) {
var filt = _.findWhere(self.filterFormats, {
Id: fmt.Id
});
if (filt) {
filt.enabled(true);
}
});
self.theatre(data);
}
});
};
self.changeFormat = function(format) {
console.log(format);
self.selectedFormat(format);
self.movies();
};
self.selectedDate.subscribe(function(newVal) {
self.getPerformances();
});
self.getPerformances();
self.arrow = function () {
alert("button clicked");
var active_el = $(this);
$('.movie-listing-header').each(function () {
if ($(this).get(0) === active_el.parent().get(0)) {
if ($(this).hasClass('active')) {
$(this).siblings('.showtimes').hide();
} else {
$(this).siblings('.showtimes').show();
}
$(this).toggleClass('active');
} else {
$(this).removeClass('active');
$(this).siblings('.showtimes').hide();
}
});
}
}
I have a feeling that var active_el = $(this) is not what you're expecting. I'm not running the code but I believe that this will be self in this context. However, I'd like to recommend a more fundamental MVVM change. Rather than including all this jQuery code for updating the UI, I would recommend setting properties on your view model instead. Here's a simplified example:
HTML
<section data-bind="foreach: movies">
<article>
<div data-bind="if: displayShowtimes">
<!-- ... show times UI -->
</div>
</article>
</section>
JavaScript
self.arrow = function (movie) {
movie.isActive(!movie.isActive());
}
This will make your JavaScript much less brittle to changes in the HTML.
I have been working on a formatting toolbar for a textarea, much like the toolbar for writing questions on this very site (with the bold, italic, underline buttons). The only difference is that I need the buttons to make the text actually bold not just add a bold tag. What I need is similar to the Instructables.com rich comment editor, or the newgrounds.com comment editor, or google docs. Does anyone have a clue how I can make some text in a textarea show up bold, italic, or underlined but keep the rest of it?
There are lots of implementations of javascript WYSIWYG eitors. FCKEditor (now CKEditor) was one of the first. TinyMCE was popular for a long time. I've recently used TinyEditor.
There's also nicedit, 10 editors listed and compared here, not to mention the answers previously provided here on SO to the question of what's available.
An they don't have to produce HTML - there are also editors producing Wiki markup
Why reinvent the wheel?
I found how to create own Rich Text Editor just like this :
(function ($) {
if (typeof $.fn.rte === "undefined") {
var defaults = {
content_css_url: "rte.css",
media_url: "images",
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;
};
var opts = $.extend(defaults, options);
return this.each(function () {
var textarea = $(this);
var iframe;
var myID = textarea.attr("id");
var settings = $.meta ? $.extend({}, opts, textarea.data()) : opts;
var sel = {
cmdBtnSelector: "." + settings.dot_net_button_class
};
function enableDesignMode() {
var content = textarea.val();
if ($.trim(content) == '') {
content = '<br />';
}
if (iframe) {
textarea.hide();
$(iframe).contents().find("body").html(content);
$(iframe).show();
$("#toolbar-" + myID).remove();
textarea.before(toolbar());
return true;
}
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 = myID;
if (textarea.attr('name')) iframe.title = textarea.attr('name');
textarea.after(iframe);
var css = "";
if (settings.content_css_url) {
css = "<link type='text/css' rel='stylesheet' href='" + settings.content_css_url + "' />";
}
var doc = "<html><head>" + css + "</head><body class='frameBody'>" + content + "</body></html>";
tryEnableDesignMode(doc, function () {
$("#toolbar-" + myID).remove();
textarea.before(toolbar());
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)
}, 250);
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();
}
}
function toolbar() {
var tb = $("<div class='rte-toolbar' id='toolbar-" + myID + "'><div>\
<p>\
<select>\
<option value=''>Block style</option>\
<option value='p'>Paragraph</option>\
<option value='h3'>Title</option>\
</select>\
</p>\
<p>\
<a href='#' class='bold'><img src='" + settings.media_url + "text_bold.png' alt='bold' title='Bold' /></a>\
<a href='#' class='italic'><img src='" + settings.media_url + "text_italic.png' alt='italic' title='Italicize' /></a>\
</p>\
<p>\
<a href='#' class='unorderedlist'><img src='" + settings.media_url + "text_list_bullets.png' alt='unordered list' title='Unordered List' /></a>\
<a href='#' class='link'><img src='" + settings.media_url + "link.png' alt='link' title='Hyperlink' /></a>\
<a href='#' class='image'><img src='" + settings.media_url + "image.png' alt='image' title='Image' /></a>\
<a href='#' class='disable'><img src='" + settings.media_url + "code.png' alt='close rte' title='View Code' /></a>\
</p></div></div>");
$('select', tb).change(function () {
var index = this.selectedIndex;
if (index != 0) {
var selected = this.options[index].value;
formatText("formatblock", '<' + selected + '>');
}
});
$('.bold', tb).click(function () {
formatText('bold');
return false;
});
$('.italic', tb).click(function () {
formatText('italic');
return false;
});
$('.unorderedlist', tb).click(function () {
formatText('insertunorderedlist');
return false;
});
$('.link', tb).click(function () {
var p = prompt("URL:");
if (p) formatText('CreateLink', p);
return false;
});
$('.image', tb).click(function () {
var p = prompt("image URL:");
if (p) formatText('InsertImage', p);
return false;
});
$('.disable', tb).click(function () {
disableDesignMode();
var edm = $('Enable design mode');
tb.empty().append(edm);
edm.click(function (e) {
e.preventDefault();
enableDesignMode();
$(this).remove();
});
return false;
});
if (settings.dot_net_button_class != null) {
var cmdBtn = $(iframe).parents('form').find(sel.cmdBtnSelector);
cmdBtn.click(function () {
disableDesignMode(true);
});
} 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) {
selection = iframe.contentWindow.document.selection;
range = selection.createRange();
try {
node = range.parentElement();
} catch (e) {
return false;
}
} else {
try {
selection = iframe.contentWindow.getSelection();
range = selection.getRangeAt(0);
} catch (e) {
return false;
}
node = range.commonAncestorContainer;
}
return node;
}
enableDesignMode();
});
};
}
})(jQuery);
The contentEditable DOM attribute is used make the document or a section of it editable. This doesn't work with a <textarea>; more often an <iframe> is created and the document inside of it used as an editor.
Mark Pilgrim has an article explaining it on the blog of the Web Hypertext Application Technology Working Group. It includes an example page showing some of the features in use.
I have got following jquery tags plugin.
I want to to restrict maxmimum 5 tags, so that user can not enter more than 5 words (separated by spaces).
Can someone please help me doing it?
Thanks.. Following is original plugin code:
(function($) {
var delimiter = new Array();
jQuery.fn.addTag = function(value,options) {
var options = jQuery.extend({focus:false},options);
this.each(function() {
id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
if (value !='') {
$('<span class="tag">'+value + ' x</span>').insertBefore('#'+id+'_addTag');
tagslist.push(value);
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
} else {
$('#'+id+'_tag').blur();
}
}
jQuery.fn.tagsInput.updateTagsField(this,tagslist);
});
return false;
};
jQuery.fn.removeTag = function(value) {
this.each(function() {
id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove();
str = '';
for (i=0; i< old.length; i++) {
if (escape(old[i])!=value) {
str = str + delimiter[id] +old[i];
}
}
jQuery.fn.tagsInput.importTags(this,str);
});
return false;
};
jQuery.fn.tagsInput = function(options) {
var settings = jQuery.extend({defaultText:'add a tag',width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false}},options);
this.each(function() {
if (settings.hide) {
$(this).hide();
}
id = $(this).attr('id')
data = jQuery.extend({
pid:id,
real_input: '#'+id,
holder: '#'+id+'_tagsinput',
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag',
},settings);
delimiter[id] = data.delimiter;
$('<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag"><input id="'+id+'_tag" value="" default="'+settings.defaultText+'" /></div><div class="tags_clear"></div></div>').insertAfter(this);
$(data.holder).css('width',settings.width);
$(data.holder).css('height',settings.height);
if ($(data.real_input).val()!='') {
jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
} else {
$(data.fake_input).val($(data.fake_input).attr('default'));
$(data.fake_input).css('color','#666666');
}
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
return false;
}
});
$(data.fake_input).bind('focus',data,function(event) {
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
$(event.data.fake_input).val('');
}
$(event.data.fake_input).css('color','#000000');
});
if (settings.autocomplete_url != undefined) {
$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
if (data) {
d = data + "";
$(event.data.real_input).addTag(d,{focus:true});
}
});;
$(data.fake_input).bind('blur',data,function(event) {
if ($(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false});
}
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
return false;
});
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
$(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('default');
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
event.preventDefault();
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
} else {
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
}
return false;
});
}
$(data.fake_input).blur();
});
return this;
};
jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {
id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
jQuery.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false});
}
};
})(jQuery);
best way is to count the number of "tag" classes already added, and then you can handle it differently, for example you can prevent showing the "add a tag" input once 5 tags inserted by defining maxTags and updating jQuery.fn.addTag and jQuery.fn.removeTag :
/*
jQuery Tags Input Plugin 1.0
Copyright (c) 2010 XOXCO, Inc
Documentation for this plugin lives here:
http://xoxco.com/clickable/jquery-tags-input
Licensed under the MIT license:
http://www.opensource.org/licenses/mit-license.php
ben#xoxco.com
*/
(function($) {
var delimiter = new Array();
var maxTags = 5;
jQuery.fn.addTag = function(value,options) {
var options = jQuery.extend({focus:false},options);
this.each(function() {
id = $(this).attr('id');
var tagslist = $(this).val().split(delimiter[id]);
if (tagslist[0] == '') {
tagslist = new Array();
}
value = jQuery.trim(value);
if (value !='') {
$('<span class="tag">'+value + ' x</span>').insertBefore('#'+id+'_addTag');
tagslist.push(value);
$('#'+id+'_tag').val('');
if (options.focus) {
$('#'+id+'_tag').focus();
} else {
$('#'+id+'_tag').blur();
}
}
jQuery.fn.tagsInput.updateTagsField(this,tagslist);
});
if($(".tag").length>maxTags-1){$('#'+id+'_addTag').hide()}
return false;
};
jQuery.fn.removeTag = function(value) {
this.each(function() {
id = $(this).attr('id');
var old = $(this).val().split(delimiter[id]);
$('#'+id+'_tagsinput .tag').remove();
str = '';
for (i=0; i< old.length; i++) {
if (escape(old[i])!=value) {
str = str + delimiter[id] +old[i];
}
}
jQuery.fn.tagsInput.importTags(this,str);
});
if($(".tag").length<maxTags){$('#'+id+'_addTag').show()}
return false;
};
jQuery.fn.tagsInput = function(options) {
var settings = jQuery.extend({defaultText:'add a tag',width:'300px',height:'100px','hide':true,'delimiter':',',autocomplete:{selectFirst:false}},options);
this.each(function() {
if (settings.hide) {
$(this).hide();
}
id = $(this).attr('id')
data = jQuery.extend({
pid:id,
real_input: '#'+id,
holder: '#'+id+'_tagsinput',
input_wrapper: '#'+id+'_addTag',
fake_input: '#'+id+'_tag',
},settings);
delimiter[id] = data.delimiter;
$('<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag"><input id="'+id+'_tag" value="" default="'+settings.defaultText+'" /></div><div class="tags_clear"></div></div>').insertAfter(this);
$(data.holder).css('width',settings.width);
$(data.holder).css('height',settings.height);
if ($(data.real_input).val()!='') {
jQuery.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
} else {
$(data.fake_input).val($(data.fake_input).attr('default'));
$(data.fake_input).css('color','#666666');
}
$(data.holder).bind('click',data,function(event) {
$(event.data.fake_input).focus();
});
// if user types a comma, create a new tag
$(data.fake_input).bind('keypress',data,function(event) {
if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
return false;
}
});
$(data.fake_input).bind('focus',data,function(event) {
if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('default')) {
$(event.data.fake_input).val('');
}
$(event.data.fake_input).css('color','#000000');
});
if (settings.autocomplete_url != undefined) {
$(data.fake_input).autocomplete(settings.autocomplete_url,settings.autocomplete).bind('result',data,function(event,data,formatted) {
if (data) {
d = data + "";
$(event.data.real_input).addTag(d,{focus:true});
}
});;
$(data.fake_input).bind('blur',data,function(event) {
if ($(event.data.fake_input).val() != $(event.data.fake_input).attr('default')) {
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:false});
}
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
return false;
});
} else {
// if a user tabs out of the field, create a new tag
// this is only available if autocomplete is not used.
$(data.fake_input).bind('blur',data,function(event) {
var d = $(this).attr('default');
if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) {
event.preventDefault();
$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true});
} else {
$(event.data.fake_input).val($(event.data.fake_input).attr('default'));
$(event.data.fake_input).css('color','#666666');
}
return false;
});
}
$(data.fake_input).blur();
});
return this;
};
jQuery.fn.tagsInput.updateTagsField = function(obj,tagslist) {
id = $(obj).attr('id');
$(obj).val(tagslist.join(delimiter[id]));
};
jQuery.fn.tagsInput.importTags = function(obj,val) {
$(obj).val('');
id = $(obj).attr('id');
var tags = val.split(delimiter[id]);
for (i=0; i<tags.length; i++) {
$(obj).addTag(tags[i],{focus:false});
}
};
})(jQuery);
How about adding something like this:
if($('.tag').length>=5){
$('#tags_tag').attr('disabled','true');
}
I put a little more flair into my demo.
Probably the easiest solution is to change line 89 of jquery.tagsinput.js from:
var skipTag = $(tagslist).tagExist(value);
to:
var skipTag = $(tagslist).length > 5 || $(tagslist).tagExist(value);