Link to home
Start Free TrialLog in
Avatar of universalglove
universalgloveFlag for United States of America

asked on

Working with tinyMCE

I could use some tactical help or some help working with tinyMCE. So if you have experience with tinyMCE, excellent!

I'm working with tinyMCE on a page containing sets of content needing editing. Each set is its own form; and in each form instances of tinyMCE are opened as needed on a textarea.

The textareas of each form contain HTML that likely includes images.
A hidden input in each form contains the URL of an image selected from the HTML content; and should, when finished, contain the URL of whichever image the user selects. (Clicking on an image indicates you are selecting that image. There can be only one.)

The goal is two-fold:

1. after or upon initializing each instance of tinyMCE, apply a class of "selected-img" to the image in the editor with src = value of the hidden input.

2. allow a user, when working with the editor's content, to click on any image and thereby have all other instances of .selected-img removed w/i that editor, .selected-img applied to the clicked image, and the URL from the src attribute of the clicked image saved to the hidden input.

FWIW, here's the structure for each form. The pertinent textarea and hidden input are the last 2 elements.
<form>
	<input type="hidden" />
	<input type="hidden" />

	<div class="listing-header">
		<img src="..." alt="" class="listing-portrait" />
		<span class="listing-metadata">metadata</span>
		...etc...
		<ul class="listing-controls">
			<li class="listing-control">
				<label>
				<input type="checkbox" />
			</li>
			...etc...
			<li class="listing-control">
				<input type="image" value="save" />
			</li>
		</ul><!-- .listing-controls -->
	</div><!-- .listing-header -->

	<div class="listing-body">
		<input type="text" />
		<textarea id="ListingContent000" name="ListingContent" class="listing-content-area">CONTENT</textarea>
		<input type="hidden" id="ListingImgURL000" name="ListingImgURL" value="selectedImgURL" />
	</div><!-- .listing-body -->

</form>

Open in new window


I have written Javascript that successfully does what I want when applied directly (not to the textarea, but to a div containing the same content); but now I have to adapt it to actually work with tinyMCE.

// Functionality to select and denote image to display with entry:
$('div.listing-content-area img').each(function() {
// To select a reference to the textarea containing each image.
	var relTextarea = $(this).parents("div[id^='ListingContent']").first();
// ... and the URL contained within its src attribute.
	var selectedImgURL = $(this).attr('src');
	
// Checks whether each image's src matches that saved in the hidden input.
	if ( selectedImgURL == relTextarea.siblings("input[id^='ListingImgURL']").first().attr('value') ) {
		$(this).addClass('selected-img');
	}
// You can ignore this piece. It just keeps any links around an image from firing when clicked on.
	$(this).parents('a').first().click(function(event) {
		event.preventDefault();
	});
	
// Adds the desired functionality that manages .selected-img, and saves the src to the hidden input.
	$(this).click(function() {
// Next line has to be here, otherwise my variables lost context by the time the image was clicked on.
		var relContainer = relTextarea;
// Here's where I remove all existing instances of .selected-img
		if ( $('img.selected-img', relContainer).length > 0 ) 
			$('img.selected-img', relContainer).removeClass('selected-img');
// Here's where I save the clicked image's src to the hidden input.
		relContainer.siblings("input[id^='ListingImgURL']").first().attr('value', $(this).attr('src'));
// Here's where I apply the class to the chosen (clicked) image.
		$(this).addClass('selected-img');
	});
});

Open in new window


So...
I'm trying to decide:
• Should I continue trying to figure out how to create a plugin for tinyMCE?
If I do, is this going to go well as far as trying to interact w/ elements outside tinyMCE and its iFrame (the hidden input)?
• Screw plugins, and just try adapting my code to target tinyMCE's iFrame directly but from outside tinyMCE?
• Screw tinyMCE, and just devise more convoluted paths to allow editing and marking the content and saving it back to a database?

One of the hang-ups I ran into (naturally) is that HTML in a textarea is not rendered by the browser. I have to have a way to not make the user work w/ HTML, but still get the content into an input that gets saved back to the database.

Using the WYSIWYG on a textarea still seems the best option to me.
Avatar of universalglove
universalglove
Flag of United States of America image

ASKER

I'm confident I could get there on my own, but I really don't want to learn all the ins and outs of tinyMCE (or any other WYSIWYG) while on vacation; and tinyMCE's documentation is rather, ummm, lacking in usefulness and clarity, in my opinion.

I have found code for tinyMCE plugins that will act when images are clicked on.

Next, I'll either determine whether a command w/i there can act outside of tinyMCE...
or I'll see whether I can target those images outside of tinyMCE.
Although, now I'm remembering I tried that already, but I don't know whether I didn't get the code right or whether tinyMCE preempts any outside interference when clicking on an image.
(That's where I could use some information on tinyMCE's internal workings...)
SOLUTION
Avatar of mcnute
mcnute
Flag of Germany image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
No, the images are from other sites. There will be unknown quantities of them within each of the textareas.

And the marked one is being noted for display later on a different, public page. All the images are already visible w/i the textarea (once tinyMCE is opened on it).

I'm essentially making an RSS feed for "members", where updates from their own sites will be displayed in one central place, but moderated. So, it's not as straight-forward as RSS feeds seem to have been meant to be - there's lots of approval and editing of the feed content.
The moderators - the users who are working with the page I'm making - will be selecting just one image (if any) for display with any teaser text.

The public page that displays the feed is already functional and in use. I'm trying to automate the back-end moderation as much as possible. The server-side script I've made that draws in the RSS feeds and saves them to a database also finds all images and saves the URL of the 1st one as a default choice.
So, now I'm trying to easily enable the moderators to select other display options - w/o having to work with the HTML of a feed's entry directly anymore. (There's lots of copying and pasting of feed content from Google Reader into the public page. It's rather appalling, and about time we automated as much of it as possible.)

So, it's not about displaying the selected image itself here, as I think you might've understood, if I'm understanding in turn the suggestion you made. It's about being able to select and note for later which image was selected, along with other changes (like marking where to cut off the feed content).
No, I've understood very well. See my code. I've taken in into account that you need the hidden textfield populated with the url of the image to send that to your database and store it. Once you click on the image in the textarea, the hidden field in your form will be populated with the value of the source of that image. Since they come from other sites their paths should be absolute in order to display in your feed.

You will send the form to some place of data organization (database) where all your settings are held. So my solution really points in the right direction.

Otherwise provide us some code and tell us more about how you store which images is to be displayed and where the text in your feed is beeing truncated. Maybe I can give you a more precise suggestion/solution.
Oh. I think I see what you're suggesting, then.
Since I have (or even if I hadn't) saved each image's URL, you're thinking to take each image and lay them all out in a separate visual list, essentially; and then the user clicks on whichever (outside the form, or not) to note which they want.

Yes, that would work.
That was actually my original plan. I'll have to remember why I veered away from that. It's been several months of working on this intermittently, with lots of distractions to go off and do other things. ... I think I steered away from the sidecar list because w/o extra effort, the images were already being displayed in the feed's content, and I just decided to make use of that instead.
So I'm doing it a harder, but more integrated, singular sort of way...
Anyway, I have gotten this far w/ a tinyMCE plugin:

(function() {
tinymce.create('tinymce.plugins.GGSelectImage', {
	init : function(ed, url) {
		var selImgURL = $(ed).siblings("input[id^='GGListingImgURL']").first().attr('value');
			
		tinymce.each(ed.dom.select('img'), function(img) {
			var imgURL = ed.dom.getAttrib(img, 'src');
			if (imgURL = selImgURL) {
				ed.dom.setAttrib(img, 'class', 'selected-img');
			}
		}); // end check for and assignment of .selected-image
		ed.execCommand('mceRepaint');

		ed.onClick.add(function(ed, evt){
			// Firefox
			if (evt.explicitOriginalTarget){ // this is the img-element
				if (evt.explicitOriginalTarget.nodeName.toLowerCase() == 'img'){
					ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
					$(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.explicitOriginalTarget, 'src'));
					ed.dom.addClass(evt.explicitOriginalTarget, 'selected-img');
					ed.execCommand('mceRepaint');
				}
			}
			// IE
			else if (evt.target) { // this is the img-element
				if (evt.target.nodeName.toLowerCase() == 'img'){
					ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
					$(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.target, 'src'));
					ed.dom.addClass(evt.target, 'selected-img');
					ed.execCommand('mceRepaint');
				}
			}
		}); // end click event
	}
});
// Register plugin using the add method
tinymce.PluginManager.add('GGselectimage', tinymce.plugins.GGSelectImage);
})();

Open in new window


It doesn't work, failing with a blank replacement of the textarea and an error in the console of "ed.dom is undefined".
I think that's because I need to figure out how to set the script in action once a new editor instance is initialized, not when tinyMCE itself is initialized (just like I wrote and ignored in my original scheme).

Now that you've reminded me of my original plan to accomplish this, if I can't get tinyMCE to work soon, I can revert back to the 1st thought. I should still have the layout and css for that 1st plan in older saved versions.

For now, I wish to obstinately pursue the current thought, now that I've gotten as far as I have with it and spent as much time on tinyMCE as I already have.
I've pasted your code into jsfiddle and got this error when clicking jslint button on top.

(function() {
tinymce.create('tinymce.plugins.GGSelectImage', {
    init : function(ed, url) {
        var selImgURL = $(ed).siblings("input[id^='GGListingImgURL']").first().attr('value');
            
        tinymce.each(ed.dom.select('img'), function(img) {
            var imgURL = ed.dom.getAttrib(img, 'src');
            if (imgURL == selImgURL) {
                ed.dom.setAttrib(img, 'class', 'selected-img');
            }
        }); // end check for and assignment of .selected-image
        ed.execCommand('mceRepaint');

        ed.onClick.add(function(ed, evt){
            // Firefox
            if (evt.explicitOriginalTarget){ // this is the img-element
                if (evt.explicitOriginalTarget.nodeName.toLowerCase() == 'img'){
                    ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
                    $(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.explicitOriginalTarget, 'src'));
                    ed.dom.addClass(evt.explicitOriginalTarget, 'selected-img');
                    ed.execCommand('mceRepaint');
                }
            }
            // IE
            else if (evt.target) { // this is the img-element
                if (evt.target.nodeName.toLowerCase() == 'img'){
                    ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
                    $(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.target, 'src'));
                    ed.dom.addClass(evt.target, 'selected-img');
                    ed.execCommand('mceRepaint');
                }
            }
        }); // end click event
    }
});
// Register plugin using the add method
tinymce.PluginManager.add('GGselectimage', tinymce.plugins.GGSelectImage);
})();

Open in new window


Maybe that solves some issues. Tell us what happened.

I've saved the fiddle so you can work with it, too.
fiddle-tinymce
Bildschirmfoto-2013-01-04-um-00..png
Oh, hehe. I apparently should have refreshed my memory on the differences between the comparison operators when I noticed them the other day elsewhere in a script. Couldn't remember what === did, and was thinking == was comparison and data type - their usage was offset in my brain by one. I've been working in VBscript too long now...

Anyway, alas... changing = to == changed nothing.

However, I'm starting off today working on a realization that I'm probably handling the plugin incorrectly. I'm trying to outright run some code while initializing the plugin; but that code needs to be run while initializing each instance of a tinyMCE editor.

Most of the plugins I'm looking over for comparison add commands and buttons and things that trigger later, but looking over them again today I found this method in one:

ed.onInit.add(function() {});

Open in new window


Going to try that out shortly.

I'm very close, though, to reverting to the list-of-images scheme that you brought me back to. I remember now why I gave up on that. I was trying to be efficient and run all the SQL queries on the page during an earlier data prep stage, but I couldn't successfully separate out the many images per one article. The recordset seemed to stuff all the images in one field, and I couldn't make it hand them back out, even though I found a webpage that specifically addressed handling that issue.

Now that you brought me back to the separate image list notion, I've been thinking about that and seeing now I could easily run a 2nd query to grab the images while cycling through the data to draw out each article's form. I'd have the right context then to easily associate each image set with its article. I was trying before to be minimalist and not have to run several queries and devise another mechanism to associate images and articles.

mcnute, thanks for being a sport and reading my journals thus far. I'll keep you posted on what works or which direction I go.
It was and will be a pleasure to accompany you.
Hoohoohoo!
It's close!
I didn't try onInit, because I found onLoadContent.
I need to work out the reference to the hidden input, and that is all it needs.
(If I hard-code or remove the pieces that refer to the hidden input (e.g. selImgURL): it successfully runs the onLoadContent part, and when I click on an image, it successfully removes the class and reapplies it to the clicked image.)

Plus, if I can't eke out a relative reference using jQuery or some other info from tinyMCE, I should be able to go the brute force route and grab what I need by slicing up the id of the editor instance and using the unique part of it to create a reference to the appropriate hidden input.

(function() {
tinymce.create('tinymce.plugins.GGSelectImage', {
	init : function(ed, url) {
		ed.onLoadContent.add(function() {
			var selImgURL = 'http://www.example.com/images/some_image.jpg';
			//var selImgURL = $(ed).siblings("input[id^='GGListingImgURL']").first().attr('value');
				
			tinymce.each(ed.dom.select('img'), function(img) {
				var imgURL = ed.dom.getAttrib(img, 'src');
				if (imgURL == selImgURL) {
					ed.dom.setAttrib(img, 'class', 'selected-img');
				}
			}); // end check for and assignment of .selected-image
			ed.execCommand('mceRepaint');
		});

		ed.onClick.add(function(ed, evt){
			// Firefox
			if (evt.explicitOriginalTarget){ // this is the img-element
				if (evt.explicitOriginalTarget.nodeName.toLowerCase() == 'img'){
					ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
					//$(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.explicitOriginalTarget, 'src'));
					ed.dom.addClass(evt.explicitOriginalTarget, 'selected-img');
					ed.execCommand('mceRepaint');
				}
			}
			// IE
			else if (evt.target) { // this is the img-element
				if (evt.target.nodeName.toLowerCase() == 'img'){
					ed.dom.removeClass(ed.dom.select('img'), 'selected-img');
					//$(ed).siblings("input[id^='GGListingImgURL']").first().attr('value', ed.dom.getAttrib(evt.target, 'src'));
					ed.dom.addClass(evt.target, 'selected-img');
					ed.execCommand('mceRepaint');
				}
			}
		}); // end click event
	}
});
// Register plugin using the add method
tinymce.PluginManager.add('GGselectimage', tinymce.plugins.GGSelectImage);
})();

Open in new window

ASKER CERTIFIED SOLUTION
Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
I've requested that this question be closed as follows:

Accepted answer: 0 points for universalglove's comment #a38746008
Assisted answer: 500 points for mcnute's comment #a38736101

for the following reason:

mcnute, I'm giving you the points and such since you were helpful (and the only responder) and did offer a good, working option for figuring out how to select the images w/o messing w/ tinyMCE. That satisfies the part of my question asking for some tactical advice.

But I am giving "Best Solution" to the final tinyMCE plugin I worked out, since that's the most ¿seamless? solution and directly works out where I was headed at the moment.

Thanks again for helping out! I do appreciate it.
Oh. Does "closing" the question mean mcnute won't get any points and the thread will disappear?
I didn't want that.
I can try to object the objection ;-) The moderator will take care of giving the points. Thank you!
Ah, okay. Messed that one up. Didn't remember it saying "closing" or offering a chance to object when I've accepted solutions normally.

Thank you all.